Votre modèle est-il aveugle au temps ? Dans le cas d’un codage de caractéristiques cyclique

: Le paradoxe de minuit

Imaginez ça. Vous construisez un modèle pour prédire la demande d’électricité ou les prises en charge des taxis. Donc, l’heure à laquelle vous le mangez (par exemple, minutes) à partir de minuit. Clair et simple. droite?

Maintenant regarde ton modèle 23h59 (1439 minutes dans une journée) Et 00:01 (1 minute par jour). Pour vous, ils sont espacés de deux minutes. Pour votre modèle, ils sont loin. C’est le paradoxe de minuit. Et oui, votre modèle est probablement aveugle au temps.

Pourquoi cela arrive-t-il ?

Parce que la plupart des modèles d’apprentissage automatique traitent les nombres comme des lignes droites et non comme des cercles.

La régression linéaire, les KNN, les SVM et même les réseaux de neurones traiteront les nombres de manière logique, en supposant que les nombres plus élevés sont « plus grands » que les nombres inférieurs. Ils ne savent pas que le temps est presque écoulé. Minuit est le cas extrême qu’ils ne pardonnent jamais.

Si vous avez déjà ajouté des données horaires à votre modèle sans succès et que vous vous demandez pourquoi votre modèle a du mal à respecter les limites quotidiennes, voici probablement pourquoi.

Échec du codage standard

Parlons de l’approche normale. Vous en avez probablement utilisé au moins un.

Vous codez les heures sous forme de nombres de 0 à 23. Il existe désormais une falaise artificielle entre l’heure 23 et l’heure 0. Ainsi, ce modèle suppose que minuit est le plus grand saut de la journée. Cependant, minuit est-il vraiment différent de 23 heures à 22 heures et à 21 heures ?

Bien sûr que non. Mais votre modèle ne le sait pas.

Voici la représentation des heures lorsqu’elles sont en mode “linéaire”.

# 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()
Heures en mode linéaire. Photo de l’auteur.

Et si nous codions des heures chaudes ? Vingt-quatre colonnes binaires. Problème résolu, non ? Eh bien… partiellement. Vous avez comblé l’écart artificiel, mais vous avez perdu la proximité. 2h du matin et non entre 22h et 3h du matin
Vous explosez en dimensionnalité. Pour les arbres, c’est ennuyeux. Pour les modèles linéaires, cela est probablement inefficace.

Passons donc à une alternative possible.

  • Solution : Cartographie trigonométrique

Voici un changement de mentalité :

Arrêtez de considérer le temps comme une ligne. Pensez-y comme à un cercle.

Une journée de 24 heures se répète. Votre encodage doit donc boucler, penser en rond. Chaque heure est un point équidistant sur un cercle. Maintenant, pour représenter un point sur un cercle, vous n’utiliseriez pas un nombre, mais plutôt Deux coordonnées: x Et oui.

C’est là qu’interviennent le sinus et le cosinus.

La géométrie derrière

Chaque coin d’un cercle peut être mappé à un point unique à l’aide de sinus et de cosinus. Cela donne à votre modèle une représentation fluide et continue du temps.

plt.figure(figsize=(5, 5))
plt.scatter(df('hour_sin'), df('hour_cos'), linewidth=3)
plt.title('Hours in Cyclical Mode')
plt.xlabel('Hour')
Heures en mode cyclique après sinus et cosinus. Photo de l’auteur.

Voici la formule mathématique pour calculer les cycles pour les heures de la journée :

  • d’abord, 2 * π * hour / 24 Convertit chaque heure en angle. Minuit et 23 heures se terminent à peu près à la même position sur le cercle.
  • alors Signe Et cosinus Projetez cet angle entre deux coordonnées.
  • Ces deux valeurs définissent ensemble de manière unique l’heure. Désormais, 23h00 et 00h00 sont proches de l’espace de fonctionnalités. Exactement ce que vous vouliez depuis le début.

Le même concept fonctionne pour les minutes, les jours de la semaine ou les mois de l’année.

code

Expérimentons avec cet ensemble de données Prévision de puissance des équipements (4). Nous essaierons d’améliorer la prédiction en utilisant un modèle de régresseur forestier aléatoire (un modèle arborescent).

Candanedo, L. (2017). Prédiction énergétique des appareils électroménagers (ensemble de données). Référentiel d’apprentissage automatique UCI. https://doi.org/10.24432/C5VC8G. Licence Creative Commons 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 

Obtenez les données.

# 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)

Construisons un modèle rapide avec ceci linéaire La première fois, comme base de comparaison.

# 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}')

Voici les résultats.

Score: 0.9395797670166536
RMSE: 63.60964667197874

Ensuite, nous allons coder les composantes temporelles cycliques (day Et hour) et recyclez le modèle.

# 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}')

Et des résultats. On constate une amélioration de 1% du score et de 1 point du RMSE.

Score: 0.9416365489096074
RMSE: 62.87008070927842

Je suis sûr que cela n’a l’air de rien, mais rappelons-nous que cet exemple de jouet utilise un modèle simple prêt à l’emploi, sans traitement ni nettoyage des données. Nous voyons principalement les effets des transformations sinus et cosinus.

Ce qui se passe réellement ici, c’est que, dans la vraie vie, la demande d’électricité ne se réinitialise pas à minuit. Et maintenant, votre modèle voit enfin cette cohérence.

Pourquoi avez-vous besoin à la fois du sinus et du cosinus

Ne soyez pas tenté de l’utiliser Signer seulementCela semble suffisant. Une colonne au lieu de deux. Plus propre, non ?

Malheureusement, cela brise la symétrie. Dans une horloge de 24 heures, 6 heures du matin et 18 heures peuvent produire la même valeur sinusoïdale. Des heures différentes avec un codage identique peuvent être pires car le modèle confond désormais l’heure de pointe du soir avec l’heure de pointe du matin. Ce n’est donc pas idéal, sauf si vous aimez les prédictions confuses.

L’utilisation à la fois du sinus et du cosinus résout ce problème. Ensemble, ils donnent à chaque tour d’heure une empreinte unique. Pensez-y comme à la latitude et à la longitude. Vous avez besoin des deux pour savoir où vous êtes.

Implications et conséquences dans le monde réel

Alors, est-ce que cela aide réellement les modèles ? oui Surtout quelque chose de spécifique.

Modèle basé sur la distance

Les KNN et les SVM s’appuient fortement sur le calcul de distance. Le codage cyclique empêche le maillage de « longues distances » à la frontière. Vos voisins sont à nouveau voisins.

Réseau neuronal

Les réseaux de neurones apprennent plus rapidement grâce à des espaces de fonctionnalités fluides. Le codage cyclique élimine les discontinuités marquées à minuit. Cela signifie généralement une convergence plus rapide et une meilleure stabilité.

Modèle basé sur un arbre

Un arbre amélioré par dégradé comme XGBoost ou LightGBM peut éventuellement apprendre ces modèles. L’encodage cyclique leur donne une longueur d’avance. Cela en vaut la peine si vous vous souciez des performances et de l’interprétabilité.

7. Quand faut-il l’utiliser ?

Posez-vous toujours la question : Cette fonctionnalité se répète-t-elle dans un cycle ? Si oui, envisagez le codage cyclique.

Les exemples courants sont :

  • heures de la journée
  • jour de la semaine
  • mois de l’année
  • Direction du vent (degrés)
  • S’il s’agit d’une boucle, vous pouvez essayer de l’encoder sous forme de boucle.

avant de partir

Le temps n’est pas qu’un chiffre. C’est une coordonnée d’un cercle.

Si vous la traitez comme une ligne droite, votre modèle peut trébucher au-delà des limites et avoir du mal à comprendre que la variable est un cycle, quelque chose qui se répète et qui présente un modèle.

L’encodage cyclique avec sinus et cosinus corrige bien ce problème, préserve la proximité, réduit les artefacts et aide les modèles à apprendre plus rapidement.

Alors la prochaine fois que vos prédictions semblent bizarres à mesure que le jour change, essayez ce nouvel outil que vous avez appris et laissez-le faire briller votre modèle comme il se doit.

Si vous aimez ce contenu, retrouvez plus de mon travail et mes contacts sur mon site internet.

https://gustavorsantos.me

Dépôt GitHub

Voici le code complet de cet exercice.

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

Références et lectures complémentaires

(1. Échange de pile d’heures d’encodage) : https://stats.stackexchange.com/questions/451295/encoding-cyclical-feature-minutes-and-hours

(2. Fonctions trigonométriques NumPy) : https://numpy.org/doc/stable/reference/routines.math.html

(3. Discussion pratique sur les propriétés cycliques) :
https://www.kaggle.com/code/avanwyk/encoding-cyclical-features-for-deep-learning

(4. Ensemble de données de prévision énergétique des appareils électroménagers) https://archive.ics.uci.edu/dataset/374/appliances+energy+prediction

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici