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

Is jouw model tijdblind? Het geval van cyclische kenmerkcodering

2
0
Is jouw model tijdblind? Het geval van cyclische kenmerkcodering

: De middernachtparadox

Stel je dit eens voor. U bouwt een model om de elektriciteitsvraag of het taxitarief te voorspellen. Vervolgens geeft u het de tijd (zoals minuten) vanaf middernacht. Schoon en eenvoudig. Rechts?

Nu ziet uw model het 23.59 uur (minuut 1439 van de dag) EN 00:01 (minuut 1 van de dag). Voor jou ben ik twee minuten verwijderd. Voor jouw model liggen ze erg ver uit elkaar. Dit 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 en zelfs neurale netwerken zullen getallen logisch behandelen, ervan uitgaande dat hogere getallen ‘meer’ zijn dan lagere. Ze weten niet dat de tijd tikt. Middernacht is het extreme geval dat ze nooit vergeven.

Als u ooit zonder succes informatie per uur aan uw model hebt toegevoegd en u zich later afvroeg waarom uw model moeite heeft om aan de dagelijkse limieten te voldoen, is dit waarschijnlijk de reden.

Het falen van standaardcodering

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

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

Uiteraard niet. Maar jouw model weet dat niet.

Hier is de weergave van de uren in de “lineaire” modus.

# 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. Afbeelding van de auteur.

Wat als we de uren in één klap zouden coderen? Vierentwintig binaire kolommen. Probleem opgelost, toch? Nou… gedeeltelijk. Je hebt de kunstmatige kloof hersteld, maar je bent de nabijheid kwijt. 02.00 uur is niet dichter bij 03.00 uur dan bij 22.00 uur.
Je hebt ook de dimensionaliteit opgeblazen. Voor bomen is het vervelend. Voor lineaire modellen is dit waarschijnlijk inefficiënt.

Laten we dus verder gaan met een haalbaar alternatief.

  • De oplossing: trigonometrische mapping

Hier is de mentaliteitsverandering:

Denk niet langer aan tijd als een lijn. Zie het als een cirkel.

Een dag van 24 uur keert terug naar zichzelf. Je codering moet dus ook in een lus plaatsvinden, waarbij je in cirkels denkt. Elk uur is een gelijkmatig verdeeld punt op een cirkel. Om een ​​punt op een cirkel weer te geven, gebruik je geen getal, maar in plaats daarvan twee coördinaten: X EN Ja.

Hier komen sinus en cosinus in het spel.

De geometrie erachter

Elke hoek op 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 na sinus en cosinus. Afbeelding van de auteur.

Hier is de wiskundige formule om de cycli voor de 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.
  • Dan borst EN cosinus projecteer die hoek in twee coördinaten.
  • Deze twee waarden definiëren samen op unieke wijze de tijd. Nu liggen 23.00 uur en 12.00 uur dicht bij elkaar in de speciale ruimte. 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 huishoudelijke apparaten (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 van huishoudelijke apparaten (dataset). UCI Machine Learning-opslagplaats. https://doi.org/10.24432/C5VC8G. Creative Commons-licentie 4.0.

# 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 ophalen.

# 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 snelle sjabloon maken met het bestand lineair eerste keer, als 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 het niet veel lijkt, maar onthoud 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 in het echte leven de vraag naar elektriciteit zich niet om middernacht herstelt. En nu ziet uw model eindelijk die continuïteit.

Omdat je zowel sinus als cosinus nodig hebt

Val niet in de verleiding om het te gebruiken alleen borstaangezien het genoeg lijkt. Eén kolom in plaats van twee. Schoner, toch?

Helaas doorbreekt het de symmetrie. Op een 24-uursklok kunnen 6.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. Niet ideaal dus, tenzij je van verwarrende voorspellingen houdt.

Het gebruik van zowel sinus als cosinus lost dit probleem op. Samen geven ze elk uur een unieke afdruk op de cirkel. Zie het als breedte- en lengtegraad. Je hebt beide nodig om te weten waar je aan toe bent.

Impact en resultaten in de echte wereld

Helpt dit modellen echt? JA. Vooral zeker.

Op afstand gebaseerde modellen

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

Neurale netwerken

Neurale netwerken leren sneller dankzij soepele featureruimtes. Cyclisch coderen verwijdert sterke discontinuïteiten om middernacht. Dit betekent doorgaans snellere convergentie en betere stabiliteit.

Op bomen gebaseerde modellen

Bomen met een verloopversterking zoals XGBoost of LightGBM kunnen deze modellen uiteindelijk leren. Cyclisch coderen geeft hen een voordeel. Als je om prestaties en interpreteerbaarheid geeft, is het de moeite waard.

7. Wanneer moet je het gebruiken?

Stel jezelf altijd de vraag: Herhaalt deze functie zich in een lus? Zo ja, overweeg dan cyclisch coderen.

Veel voorkomende voorbeelden zijn:

  • Tijd van de dag
  • Dag van de week
  • Maand van het jaar
  • Windrichting (graden)
  • Als het in een lus zit, kun je proberen het als een lus te coderen.

Voordat je gaat

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

Als je het als een rechte lijn behandelt, kan je model over beperkingen struikelen en moeite hebben om die variabele als een cyclus te begrijpen, iets dat zich herhaalt en een patroon heeft.

Cyclisch coderen met sinus en cosinus lost dit probleem op elegante wijze op, waarbij de nabijheid behouden blijft, artefacten worden verminderd en modellen sneller leren.

Dus de volgende keer dat uw voorspellingen vreemd lijken over de veranderingen van de dag, 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, vind dan meer van mijn werk en mijn contacten 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. Uren coderen 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 discussie over cyclische kenmerken):
https://www.kaggle.com/code/avanwyk/encoding-cyclical-features-for-deep-learning

(4. Dataset voor energievoorspelling voor huishoudelijke 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