Home Nieuws Is jouw model tijdblind? Het geval van cyclische functiecodering

Is jouw model tijdblind? Het geval van cyclische functiecodering

14
0
Is jouw model tijdblind? Het geval van cyclische functiecodering

: De middernachtparadox

Stel je dit eens voor. Je bouwt een model om de vraag naar elektriciteit of het ophalen van taxi’s te voorspellen. Dus je geeft het tijd (zoals minuten) vanaf middernacht. Schoon en eenvoudig. Rechts?

Bekijk nu uw model 23:59 (minuut 1439 van de dag) En 00:01 (minuut 1 van de dag). Voor jou liggen ze twee minuten uit elkaar. Voor jouw model liggen ze erg ver uit elkaar. Het is de middernachtparadox. En ja, jouw model is waarschijnlijk tijdblind.

Waarom gebeurt dit?

Omdat de meeste machine learning-modellen getallen behandelen als rechte lijnen en niet als cirkels.

Lineaire regressie, KNN, SVM’s en zelfs neurale netwerken zullen getallen logisch behandelen, ervan uitgaande dat hogere getallen ‘meer’ zijn dan lagere. Ze weten niet dat de tijd vliegt. Middernacht is het randgeval, ze vergeven het nooit.

Als u ooit zonder succes uurinformatie aan uw model heeft toegevoegd en u zich later afvraagt ​​waarom uw model het de hele dag moeilijk heeft, dan is dit waarschijnlijk de reden.

De standaardcoderingsfout

Laten we het hebben over de gebruikelijke benaderingen. Je hebt er waarschijnlijk minstens één gebruikt.

Je codeert uren als getallen van 0 tot en met 23. Nu is er een kunstmatige klif tussen uur 23 en uur 0. Daarom gelooft dit model dat middernacht de grootste sprong van de dag is. Maar verschilt middernacht echt meer van 23.00 uur, dan om 22.00 uur vanaf 21.00 uur?

Natuurlijk niet. Maar jouw model weet dat niet.

Hier is de timerweergave wanneer ze zich in de “lineaire” modus bevinden.

# Generate data
date_today = pd.to_datetime('today').normalize()
datetime_24_hours = pd.date_range(start=date_today, periods=24, freq='h')
df = pd.DataFrame({'dt': datetime_24_hours})
df('hour') = df('dt').dt.hour	

# Calculate Sin and Cosine
df("hour_sin") = np.sin(2 * np.pi * df("hour") / 24)
df("hour_cos") = np.cos(2 * np.pi * df("hour") / 24)

# Plot the Hours in Linear mode
plt.figure(figsize=(15, 5))
plt.plot(df('hour'), (1)*24, linewidth=3)
plt.title('Hours in Linear Mode')
plt.xlabel('Hour')
plt.xticks(np.arange(0, 24, 1))
plt.ylabel('Value')
plt.show()
Uren in lineaire modus. Foto van de auteur.

Wat als we de uren in één keer coderen? Vierentwintig binaire kolommen. Probleem opgelost, toch? Nou ja… gedeeltelijk. Je hebt de kunstmatige kloof gedicht, maar je bent de nabijheid kwijtgeraakt. 02.00 uur ligt niet dichter bij 03.00 uur dan 22.00 uur.
Je hebt ook de dimensionaliteit geëxplodeerd. Voor bomen is het vervelend. Voor lineaire modellen is het waarschijnlijk inefficiënt.

Laten we dus verder gaan met een mogelijk alternatief.

  • De oplossing: trigonometrische kaarten

Hier is de mentaliteitsverandering:

Beschouw de tijd niet als een lijn. Zie het als een cirkel.

Een dag van 24 uur gaat vanzelf terug. Je codering moet dus ook in een lus plaatsvinden, in cirkels denken. Elk uur is een gelijkmatig verdeeld punt op een cirkel. Om een ​​punt op een cirkel weer te geven, gebruik je niet één getal, maar in plaats daarvan gebruik je twee coördinaten: X En j.

Dit is waar sinus en cosinus binnenkomen.

De geometrie erachter

Elke hoek van een cirkel kan met behulp van sinus en cosinus worden toegewezen aan een uniek punt. Hierdoor krijgt uw model een vloeiende, continue weergave van de tijd.

plt.figure(figsize=(5, 5))
plt.scatter(df('hour_sin'), df('hour_cos'), linewidth=3)
plt.title('Hours in Cyclical Mode')
plt.xlabel('Hour')
Uren in cyclische modus volgens sinus en cosinus. Foto van de auteur.

Hier is de wiskundige formule om cycli voor uren van de dag te berekenen:

  • Eerst, 2 * π * hour / 24 converteert elk uur naar een hoek. Middernacht en 23.00 uur eindigen op bijna dezelfde positie op de cirkel.
  • Dus sinus En cosinus projecteer deze hoek in twee coördinaten.
  • Deze twee waarden definiëren op unieke wijze het uur. Nu zijn 23:00 en 00:00 bijna de ruimte voor een functie. Precies wat je al die tijd wilde.

Hetzelfde idee werkt voor minuten, dagen van de week of maanden van het jaar.

Code

Laten we experimenteren met deze dataset Energievoorspelling van het apparaat (4). We zullen proberen de voorspelling te verbeteren met behulp van een Random Forest Regressor-model (een op bomen gebaseerd model).

Candanedo, L. (2017). Energievoorspelling voor apparaten (dataset). UCI Machine Learning-opslagplaats. https://doi.org/10.24432/C5VC8G. Creative Commons 4.0-licentie.

# Imports
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import root_mean_squared_error
from ucimlrepo import fetch_ucirepo 

Gegevens downloaden.

# fetch dataset 
appliances_energy_prediction = fetch_ucirepo(id=374) 
  
# data (as pandas dataframes) 
X = appliances_energy_prediction.data.features 
y = appliances_energy_prediction.data.targets 
  
# To Pandas
df = pd.concat((X, y), axis=1)
df('date') = df('date').apply(lambda x: x(:10) + ' ' + x(11:))
df('date') = pd.to_datetime(df('date'))
df('month') = df('date').dt.month
df('day') = df('date').dt.day
df('hour') = df('date').dt.hour
df.head(3)

Laten we een snel model maken met lineair tijd eerst, als onze basis voor vergelijking.

# X and y
# X = df.drop(('Appliances', 'rv1', 'rv2', 'date'), axis=1)
X = df(('hour', 'day', 'T1', 'RH_1', 'T_out', 'Press_mm_hg', 'RH_out', 'Windspeed', 'Visibility', 'Tdewpoint'))
y = df('Appliances')

# Train Test Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Fit the model
lr = RandomForestRegressor().fit(X_train, y_train)

# Score
print(f'Score: {lr.score(X_train, y_train)}')

# Test RMSE
y_pred = lr.predict(X_test)
rmse = root_mean_squared_error(y_test, y_pred)
print(f'RMSE: {rmse}')

De resultaten zijn hier.

Score: 0.9395797670166536
RMSE: 63.60964667197874

Vervolgens zullen we de cyclische tijdcomponenten coderen (day En hour) en train het model opnieuw.

# Add cyclical hours sin and cosine
df('hour_sin') = np.sin(2 * np.pi * df('hour') / 24)
df('hour_cos') = np.cos(2 * np.pi * df('hour') / 24)
df('day_sin') = np.sin(2 * np.pi * df('day') / 31)
df('day_cos') = np.cos(2 * np.pi * df('day') / 31)

# X and y
X = df(('hour_sin', 'hour_cos', 'day_sin', 'day_cos','T1', 'RH_1', 'T_out', 'Press_mm_hg', 'RH_out', 'Windspeed', 'Visibility', 'Tdewpoint'))
y = df('Appliances')

# Train Test Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Fit the model
lr_cycle = RandomForestRegressor().fit(X_train, y_train)

# Score
print(f'Score: {lr_cycle.score(X_train, y_train)}')

# Test RMSE
y_pred = lr_cycle.predict(X_test)
rmse = root_mean_squared_error(y_test, y_pred)
print(f'RMSE: {rmse}')

En de resultaten. We zien een verbetering van 1% in de score en 1 punt in RMSE.

Score: 0.9416365489096074
RMSE: 62.87008070927842

Ik weet zeker dat dit er niet veel uitziet, maar laten we niet vergeten dat dit speelgoedvoorbeeld een eenvoudig kant-en-klaar model gebruikt zonder enige gegevensverwerking of opschoning. We zien vooral het effect van de sinus- en cosinustransformatie.

Wat hier werkelijk gebeurt, is dat de werkelijke vraag naar elektriciteit niet om middernacht wordt gereset. En nu ziet uw model eindelijk die continuïteit.

Waarom je zowel sinus als cosinus nodig hebt

Laat u niet verleiden tot geld uitgeven alleen sinusals het genoeg voelt. Eén kolom in plaats van twee. Schoner, toch?

Helaas doorbreekt het de symmetrie. Op een 24-uursklok kunnen 06.00 uur en 18.00 uur dezelfde sinuswaarde produceren. Verschillende tijden met identieke codering kunnen slecht zijn, omdat het model nu de ochtendspits met de avondspits verwart. Dus niet ideaal, tenzij je van warrige voorspellingen houdt.

Door zowel sinus als cosinus te gebruiken, wordt dit opgelost. Samen geven ze elk uur een unieke vingerafdruk op de cirkel. Zie het als breedte- en lengtegraad. Je hebt beide nodig om te weten waar je bent.

Impact en resultaten in de echte wereld

Helpt dit modellen eigenlijk? Ja. Vooral bepaalde.

Op afstand gebaseerde modellen

KNN en SVM’s zijn sterk afhankelijk van afstandsberekeningen. Cyclische codering voorkomt valse “lange afstanden” bij grenzen. Je buren worden eigenlijk weer buren.

Neurale netwerken

Neurale netwerken leren sneller dankzij soepele featureruimtes. Cyclische codering verwijdert scherpe discontinuïteiten om middernacht. Dit betekent doorgaans snellere convergentie en betere stabiliteit.

Modellen op houtbasis

Bomen met verloopversterking zoals XGBoost of LightGBM kunnen deze patronen in de loop van de tijd leren. Cyclische codering geeft hen een voorsprong. Als je om uitvoering en interpretatie geeft, is het de moeite waard.

7. Wanneer moet je dit gebruiken?

Stel jezelf altijd de vraag: Herhaalt deze functie zich in een cyclus? Als dat zo is, overweeg dan cyclisch coderen.

Veel voorkomende voorbeelden zijn:

  • Uur van de dag
  • dag van de week
  • Maand van het jaar
  • Windrichting (graden)
  • Als het in een lus zit, probeer het dan als een lus te coderen.

Voordat je gaat

Tijd is niet slechts een getal. Het is een coördinaat op een cirkel.

Als u het als een rechte lijn beschouwt, kan uw model over grenzen heen struikelen en moeite hebben om die variabele als een cyclus te begrijpen, iets dat zich herhaalt en een patroon heeft.

Cyclische sinus- en cosinuscodering lost dit op elegante wijze op, waarbij de nabijheid behouden blijft, artefacten worden verminderd en modellen sneller leren.

Dus de volgende keer dat uw voorspellingen er vreemd uitzien bij dagelijkse veranderingen, probeer dan dit nieuwe hulpmiddel dat u heeft geleerd en laat het uw model laten schitteren zoals het hoort.

Als je deze inhoud leuk vond, kun je meer van mijn werk en contacten vinden op mijn website.

https://gustavorsantos.me

GitHub-opslagplaats

Hier is de volledige code voor deze oefening.

https://github.com/gurezende/Time-Series/tree/main/Sine%20Cosine%20Time%20Encode

Referenties en verder lezen

(1. Codeerlessen Stack Exchange): https://stats.stackexchange.com/questions/451295/encoding-cyclical-feature-minutes-and-hours

(2. NumPy trigonometrische functies): https://numpy.org/doc/stable/reference/routines.math.html

(3. Praktische bespreking van cyclische kenmerken):
https://www.kaggle.com/code/avanwyk/encoding-cyclical-features-for-deep-learning

(4. Gegevensset voor energievoorspelling van apparaten) https://archive.ics.uci.edu/dataset/374/appliances+energy+prediction

Nieuwsbron

LAAT EEN REACTIE ACHTER

Vul alstublieft uw commentaar in!
Vul hier uw naam in