Modélisation des caractéristiques saisonnières variables avec la transformation de Fourier

Modélisation des variations saisonnières des caractéristiques à l'aide de la transformation de Fourier

Améliorez les performances de prévision des séries chronologiques avec une technique provenant du traitement du signal

La modélisation des données de séries chronologiques et leur prévision sont des sujets complexes. Il existe de nombreuses techniques qui pourraient être utilisées pour améliorer les performances du modèle pour une tâche de prévision. Nous discuterons ici d’une technique qui pourrait améliorer la façon dont les modèles de prévision absorbent et utilisent les caractéristiques temporelles, et en généralisent à partir d’elles. L’accent principal sera mis sur la création des caractéristiques saisonnières qui alimentent le modèle de prévision des séries chronologiques lors de l’entraînement – il y a des gains faciles à réaliser ici si vous incluez la transformation de Fourier dans le processus de création des caractéristiques.

Cet article suppose que vous êtes familiarisé avec les aspects fondamentaux de la prévision des séries chronologiques – nous ne discuterons pas de ce sujet en général, mais seulement d’un aspect de son raffinement. Pour une introduction à la prévision des séries chronologiques, consultez le cours Kaggle sur ce sujet – la technique discutée ici s’appuie sur leur leçon sur la saisonnalité.

Ensemble de données

Considérez l’ensemble de données Production trimestrielle de Portland Cement en Australie, montrant la production trimestrielle totale de ciment Portland en Australie (en millions de tonnes) de 1956:Q1 à 2014:Q1.

df = pd.read_csv('Quarterly_Australian_Portland_Cement_production_776_10.csv', usecols=['time', 'value'])# convert time from year float to a proper datetime formatdf['time'] = df['time'].apply(lambda x: str(int(x)) + '-' + str(int(1 + 12 * (x % 1))).rjust(2, '0'))df['time'] = pd.to_datetime(df['time'])df = df.set_index('time').to_period()df.rename(columns={'value': 'production'}, inplace=True)

        productiontime              1956Q1       0.4651956Q2       0.5321956Q3       0.5611956Q4       0.5701957Q1       0.529...            ...2013Q1       2.0492013Q2       2.5282013Q3       2.6372013Q4       2.5652014Q1       2.229[233 rows x 1 columns]

Il s’agit de données de séries chronologiques avec une tendance, des composantes saisonnières et d’autres attributs. Les observations sont effectuées trimestriellement, couvrant plusieurs décennies. Jetons d’abord un coup d’œil aux composantes saisonnières, en utilisant le graphique du périodogramme (le code est inclus dans le notebook compagnon, lié à la fin).

périodogramme

Le périodogramme montre la densité de puissance des composantes spectrales (composantes saisonnières). La composante la plus forte est celle avec une période égale à 4 trimestres, soit 1 an. Cela confirme l’impression visuelle selon laquelle les variations les plus fortes dans le graphique se produisent environ 10 fois par décennie. Il y a également une composante avec une période de 2 trimestres – c’est le même phénomène saisonnier, et cela signifie simplement que la périodicité de 4 trimestres n’est pas une simple onde sinusoïdale, mais a une forme plus complexe. Nous ignorerons les composantes avec des périodes supérieures à 4, pour plus de simplicité.

Si vous essayez d’ajuster un modèle à ces données, peut-être afin de prévoir la production de ciment pour des périodes au-delà de la fin de l’ensemble de données, il serait judicieux de capturer cette saisonnalité annuelle dans une caractéristique ou un ensemble de caractéristiques, et de les fournir au modèle lors de l’entraînement. Voyons un exemple.

Caractéristiques saisonnières

Les composantes saisonnières peuvent être modélisées de différentes manières. Souvent, elles sont représentées sous forme de variables indicatrices temporelles, ou sous forme de paires sinusoïdales-cosinusoidales. Ce sont des caractéristiques synthétiques avec une période égale à la saisonnalité que vous essayez de modéliser, ou à un sous-multiple de celle-ci.

Variables indicatrices temporelles annuelles :

seasonal_year = ProcessusDéterministe(indice=df.indice, constant=False, saisonnier=True).dans_échantillon()print(seasonal_year)

        s(1,4)  s(2,4)  s(3,4)  s(4,4)temps                                  1956T1     1.0     0.0     0.0     0.01956T2     0.0     1.0     0.0     0.01956T3     0.0     0.0     1.0     0.01956T4     0.0     0.0     0.0     1.01957T1     1.0     0.0     0.0     0.0...        ...     ...     ...     ...2013T1     1.0     0.0     0.0     0.02013T2     0.0     1.0     0.0     0.02013T3     0.0     0.0     1.0     0.02013T4     0.0     0.0     0.0     1.02014T1     1.0     0.0     0.0     0.0[233 rows x 4 colonnes]

Paires sinus-cosinus annuelles :

cfr = CalendrierFourier(fréquence='A', ordre=2)seasonal_year_trig = ProcessusDéterministe(indice=df.indice, saisonnier=False, termes_supplémentaires=[cfr]).dans_échantillon()with pd.option_context('display.max_columns', None, 'display.expand_frame_repr', False):    print(seasonal_year_trig)

        sin(1,freq=A-DEC)  cos(1,freq=A-DEC)  sin(2,freq=A-DEC)  cos(2,freq=A-DEC)temps                                                                              1956T1           0.000000           1.000000           0.000000           1.0000001956T2           0.999963           0.008583           0.017166          -0.9998531956T3           0.017166          -0.999853          -0.034328           0.9994111956T4          -0.999963          -0.008583           0.017166          -0.9998531957T1           0.000000           1.000000           0.000000           1.000000...                   ...                ...                ...                ...2013T1           0.000000           1.000000           0.000000           1.0000002013T2           0.999769           0.021516           0.043022          -0.9990742013T3           0.025818          -0.999667          -0.051620           0.9986672013T4          -0.999917          -0.012910           0.025818          -0.9996672014T1           0.000000           1.000000           0.000000           1.000000[233 rows x 4 colonnes]

Les variables bidimensionnelles permettent de capturer des phénomènes saisonniers complexes. Cependant, si la période est de N, vous aurez besoin de N variables bidimensionnelles. Donc, si la période est très longue, vous aurez besoin de beaucoup de colonnes pour ces variables, ce qui peut ne pas être souhaitable. Pour les modèles non pénalisés, souvent N-1 variables bidimensionnelles sont utilisées, avec une variable laissée de côté pour éviter les problèmes de colinéarité (nous ne prendrons pas cela en compte ici).

Les paires sinus/cosinus permettent de modéliser des périodes de temps arbitrairement longues. Chaque paire modélisera une onde sinusoïdale pure avec une phase initiale. À mesure que l’onde du phénomène saisonnier devient plus complexe, vous devrez ajouter plus de paires (augmenter l’ordre de la sortie de CalendrierFourier).

(Note : nous utilisons une paire sinus/cosinus pour chaque période que nous voulons modéliser. Ce que nous voulons vraiment, c’est juste une colonne de A*sin(2*pi*t/T + phi)A est le poids attribué par le modèle à la colonne, t est l’indice de temps de la série et T est la période composante. Mais le modèle ne peut pas ajuster la phase initiale phi lors de l’ajustement des données – les valeurs sinus sont précalculées. Heureusement, la formule ci-dessus est équivalente à : A1*sin(2*pi*t/T) + A2*cos(2*pi*t/T) et le modèle n’a besoin que de trouver les poids A1 et A2.)

TLDR: Ce que ces deux techniques ont en commun, c’est qu’elles représentent la saisonnalité via un ensemble de caractéristiques répétitives, avec des valeurs qui se cyclent aussi souvent qu’une fois par période de temps modélisée (time dummies et la paire sine/cosine de base), ou plusieurs fois par période (sine/cosine d’ordre supérieur). Et chacune de ces caractéristiques a des valeurs variant dans un intervalle fixe : de 0 à 1 ou de -1 à 1. Ce sont toutes les caractéristiques que nous devons modéliser ici.

Voyons ce qui se passe lorsque nous ajustons un modèle linéaire sur de telles caractéristiques saisonnières. Mais d’abord, nous devons ajouter des caractéristiques de tendance à la collection de caractéristiques utilisée pour former le modèle.

Ajuster un modèle linéaire

Créons des caractéristiques de tendance puis joignons-les aux time dummies seasonnelles générées ci-dessus :

trend_order = 2trend_year = DeterministicProcess(index=df.index, constant=True, order=trend_order).in_sample()X = trend_year.copy()X = X.join(seasonal_year)

        const  trend  trend_squared  s(1,4)  s(2,4)  s(3,4)  s(4,4)time                                                               1956Q1    1.0    1.0            1.0     1.0     0.0     0.0     0.01956Q2    1.0    2.0            4.0     0.0     1.0     0.0     0.01956Q3    1.0    3.0            9.0     0.0     0.0     1.0     0.01956Q4    1.0    4.0           16.0     0.0     0.0     0.0     1.01957Q1    1.0    5.0           25.0     1.0     0.0     0.0     0.0...       ...    ...            ...     ...     ...     ...     ...2013Q1    1.0  229.0        52441.0     1.0     0.0     0.0     0.02013Q2    1.0  230.0        52900.0     0.0     1.0     0.0     0.02013Q3    1.0  231.0        53361.0     0.0     0.0     1.0     0.02013Q4    1.0  232.0        53824.0     0.0     0.0     0.0     1.02014Q1    1.0  233.0        54289.0     1.0     0.0     0.0     0.0[233 rows x 7 columns]

C’est le dataframe X (caractéristiques) qui sera utilisé pour former/valider le modèle. Nous modélisons les données avec des caractéristiques de tendance quadratique, plus les 4 time dummies nécessaires pour la saisonnalité annuelle. Le dataframe y (cible) contiendra simplement les chiffres de production de ciment.

Découpons un ensemble de validation à partir des données, contenant les observations de l’année 2010. Nous ajusterons un modèle linéaire sur les caractéristiques X mentionnées ci-dessus et la cible y représentée par la production de ciment (la partie de test), puis nous évaluerons la performance du modèle en validation. Nous ferons également tout cela avec un modèle basé uniquement sur la tendance qui ajustera uniquement les caractéristiques de tendance mais ignorera la saisonnalité.

def do_forecast(X, index_train, index_test, trend_order):    X_train = X.loc[index_train]    X_test = X.loc[index_test]    y_train = df['production'].loc[index_train]    y_test = df['production'].loc[index_test]    model = LinearRegression(fit_intercept=False)    _ = model.fit(X_train, y_train)    y_fore = pd.Series(model.predict(X_test), index=index_test)    y_past = pd.Series(model.predict(X_train), index=index_train)    trend_columns = X_train.columns.to_list()[0 : trend_order + 1]    model_trend = LinearRegression(fit_intercept=False)    _ = model_trend.fit(X_train[trend_columns], y_train)    y_trend_fore = pd.Series(model_trend.predict(X_test[trend_columns]), index=index_test)    y_trend_past = pd.Series(model_trend.predict(X_train[trend_columns]), index=index_train)    RMSLE = mean_squared_log_error(y_test, y_fore, squared=False)    print(f'RMSLE : {RMSLE}')    ax = df.plot(**plot_params, title='AUS Cement Production - Forecast')    ax = y_past.plot(color='C0', label='Backcast')    ax = y_fore.plot(color='C3', label='Forecast')    ax = y_trend_past.plot(ax=ax, color='C0', linewidth=3, alpha=0.333, label='Trend Past')    ax = y_trend_fore.plot(ax=ax, color='C3', linewidth=3, alpha=0.333, label='Trend Future')    _ = ax.legend()do_forecast(X, index_train, index_test, trend_order)

RMSLE : 0.03846449744356434
validation du modèle

Le bleu correspond à l’entraînement, le rouge à la validation. Le modèle complet est représenté par la ligne fine et nette. Le modèle basé uniquement sur la tendance est symbolisé par la ligne large et floue.

Ce n’est pas mal, mais il y a un problème évident : le modèle a appris une saisonnalité annuelle constante en amplitude. Bien que la variation annuelle augmente réellement avec le temps, le modèle ne peut adhérer qu’à des variations à amplitude fixe. Évidemment, c’est parce que nous avons donné au modèle des caractéristiques saisonnières à amplitude fixe, et il n’y a rien d’autre dans les caractéristiques (décalages de la cible, etc.) pour l’aider à surmonter ce problème.

Allons plus en profondeur dans le signal (les données) pour voir s’il y a quelque chose qui pourrait aider à résoudre le problème de l’amplitude fixe.

Le spectrogramme de Fourier

Le périodogramme mettra en évidence toutes les composantes spectrales du signal (toutes les composantes saisonnières des données) et donnera un aperçu de leur force globale, mais c’est une somme sur l’ensemble de l’intervalle de temps. Cela ne dit rien sur la manière dont l’amplitude de chaque composante saisonnière peut varier dans le temps sur l’ensemble des données.

Pour capturer cette variation, il faut utiliser le spectrogramme de Fourier. C’est comme le périodogramme, mais effectué de manière répétée sur de nombreuses fenêtres temporelles sur l’ensemble des données. Le périodogramme et le spectrogramme sont tous deux disponibles en tant que méthodes dans la bibliothèque scipy.

Traçons le spectrogramme pour les composantes saisonnières avec des périodes de 2 et 4 trimestres mentionnées ci-dessus. Comme toujours, le code complet se trouve dans le cahier d’accompagnement lié à la fin.

spectrum = compute_spectrum(df['production'], 4, 0.1)plot_spectrogram(spectrum, figsize_x=10)
spectrogramme

Ce diagramme montre l’amplitude, la “force” des composantes de 2 trimestres et 4 trimestres au fil du temps. Elles sont assez faibles initialement, mais deviennent très fortes autour de 2010, ce qui correspond aux variations que l’on peut observer dans le graphique des données en haut de cet article.

Et si, au lieu de fournir au modèle des caractéristiques saisonnières à amplitude fixe, nous ajustions l’amplitude de ces caractéristiques dans le temps, en nous basant sur ce que nous dit le spectrogramme ? Est-ce que le modèle final performera mieux lors de la validation ?

Ajuster les composantes saisonnières

Prenons la composante saisonnière de 4 trimestres. Nous pourrions ajuster un modèle linéaire (appelé modèle enveloppe) sur la tendance d’ordre 2 de cette composante, sur les données d’entraînement (model.fit()), et étendre cette tendance à la validation / aux tests (model.predict()) :

envelope_features = DeterministicProcess(index=X.index, constant=True, order=2).in_sample()spec4_train = compute_spectrum(df['production'].loc[index_train], max_period=4)spec4_model = LinearRegression()spec4_model.fit(envelope_features.loc[spec4_train.index], spec4_train['4.0'])spec4_regress = pd.Series(spec4_model.predict(envelope_features), index=X.index)ax = spec4_train['4.0'].plot(label='enveloppe de la composante', color='gray')spec4_regress.loc[spec4_train.index].plot(ax=ax, color='C0', label='régression de l'enveloppe : passé')spec4_regress.loc[index_test].plot(ax=ax, color='C3', label="régression de l'enveloppe : futur")_ = ax.legend()
ajustement de l'enveloppe

La ligne bleue représente la force de la variation de la composante de 4 trimestres dans les données d’entraînement, ajustée en tant que tendance quadratique (ordre=2). La ligne rouge est la même chose, étendue (prédite) sur les données de validation.

Nous avons modélisé la variation temporelle de la composante saisonnière de 4 trimestres. Nous pouvons prendre la sortie du modèle d’enveloppe et la multiplier par les indicateurs temporels correspondant à la composante saisonnière de 4 trimestres :

spec4_regress = spec4_regress / spec4_regress.mean()
season_columns = ['s(1,4)', 's(2,4)', 's(3,4)', 's(4,4)']
for c in season_columns:
    X[c] = X[c] * spec4_regress
print(X)

        const  tendance  tendance_au_carré    s(1,4)    s(2,4)    s(3,4)    s(4,4)temps                                                                       1956T1    1.0    1.0            1.0  0.179989  0.000000  0.000000  0.0000001956T2    1.0    2.0            4.0  0.000000  0.181109  0.000000  0.0000001956T3    1.0    3.0            9.0  0.000000  0.000000  0.182306  0.0000001956T4    1.0    4.0           16.0  0.000000  0.000000  0.000000  0.1835811957T1    1.0    5.0           25.0  0.184932  0.000000  0.000000  0.000000...       ...    ...            ...       ...       ...       ...       ...2013T1    1.0  229.0        52441.0  2.434701  0.000000  0.000000  0.0000002013T2    1.0  230.0        52900.0  0.000000  2.453436  0.000000  0.0000002013T3    1.0  231.0        53361.0  0.000000  0.000000  2.472249  0.0000002013T4    1.0  232.0        53824.0  0.000000  0.000000  0.000000  2.4911392014T1    1.0  233.0        54289.0  2.510106  0.000000  0.000000  0.000000[233 rows x 7 columns]

Les 4 dummies temporelles ne sont plus égales à 0 ou 1. Elles ont été multipliées par l’enveloppe du composant et maintenant leur amplitude varie dans le temps, tout comme l’enveloppe.

Retraner le modèle

Retraînons le modèle principal en utilisant les dummies temporelles modifiées.

do_forecast(X, index_train, index_test, trend_order)

RMSLE: 0.02546321729737165
validation du modèle, dummies temporelles ajustées

Nous ne cherchons pas ici une correspondance parfaite, car il s’agit juste d’un modèle fictif, mais il est évident que le modèle fonctionne mieux, il suit de plus près les variations trimestrielles de la cible. La métrique de performance s’est également améliorée de 51%, ce qui n’est pas mal du tout.

Commentaires finaux

L’amélioration des performances d’un modèle est un vaste sujet. Le comportement de tout modèle ne dépend pas d’une seule caractéristique ou d’une seule technique. Cependant, si vous souhaitez tirer le meilleur parti d’un modèle donné, vous devriez probablement lui fournir des caractéristiques significatives. Les dummies temporelles, ou les paires sinus/cosinus de Fourier, sont plus significatives lorsqu’elles reflètent la variation temporelle de la saisonnalité qu’elles modélisent.

L’ajustement de l’enveloppe du composant saisonnier via la transformation de Fourier est particulièrement efficace pour les modèles linéaires. Les arbres boostés n’en bénéficient pas autant, mais vous pouvez quand même observer des améliorations quelle que soit le modèle utilisé. Si vous utilisez un modèle hybride, où un modèle linéaire gère les caractéristiques déterministes (calendrier, etc.), tandis qu’un modèle d’arbres boostés gère les caractéristiques plus complexes, il est important que le modèle linéaire “fasse bien son travail”, laissant ainsi moins de travail à faire pour l’autre modèle.

Il est également important que les modèles d’enveloppe que vous utilisez pour ajuster les caractéristiques saisonnières ne soient entraînés qu’avec les données d’entraînement et ne voient aucune donnée de test pendant l’entraînement, tout comme tout autre modèle. Une fois que vous avez ajusté l’enveloppe, les variables temps contiennent des informations provenant de la cible – elles ne sont plus uniquement des caractéristiques déterministes pouvant être calculées à l’avance sur n’importe quelle plage de prévision arbitraire. À ce stade, des informations pourraient fuiter de la validation/test vers les données d’entraînement si vous n’êtes pas prudent.

L’ensemble de données utilisé dans cet article est disponible ici sous la licence Domaine Public (CC0) :

KEY2STATS

© 2023 KEY2STATS – RStudio® est une marque déposée de RStudio, Inc. AP® est une marque déposée du College…

www.key2stats.com

Le code utilisé dans cet article peut être trouvé ici :

misc/seasonal_features_fourier_article/cement.ipynb sur master · FlorinAndrei/misc

trucs aléatoires. Contribuez au développement de FlorinAndrei/misc en créant un compte sur GitHub.

github.com

Un notebook soumis à la compétition Store Sales — Time Series Forecasting sur Kaggle, utilisant des idées décrites dans cet article :

Spectrogramme de Fourier, modèles ensemblistes

Explorez et exécutez du code d’apprentissage automatique avec Kaggle Notebooks | Utilisation de données de Store Sales – Time Series Forecasting

www.kaggle.com

Un référentiel GitHub contenant la version de développement du notebook soumis à Kaggle se trouve ici :

GitHub – FlorinAndrei/timeseries-forecasting-fourier-time-dummies: Prévision de séries chronologiques avec …

Prévision de séries chronologiques avec des dummies temporelles ajustées par Fourier – GitHub …

github.com

Toutes les images et le code utilisés dans cet article ont été créés par l’auteur.

We will continue to update IPGirl; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

AI

Comment fonctionne réellement la « diffusion stable » ? Une explication intuitive

Cet court article explique de manière intuitive comment fonctionne la Diffusion Stable pour les débutants. C'est un a...

AI

Microsoft infuse l'IA avec un raisonnement semblable à celui des humains via un algorithme de pensées.

Une nouvelle technique combine les forces de la cognition humaine avec la logique algorithmique.

AI

Cette recherche en apprentissage automatique de DeepMind introduit des modèles vectoriels quantifiés (VQ) pour une planification avancée dans des environnements dynamiques

Avec les avancées constantes de la technologie, l’intelligence artificielle permet aux ordinateurs de penser et...

AI

Silicon Volley Les designers font appel à l'IA générative pour une assistance à puce

Un article de recherche publié aujourd’hui décrit comment l’IA générative peut aider l’un des effor...

AI

Un nouveau cadre théorique d'IA pour analyser et limiter les fuites d'information des modèles d'apprentissage automatique

Les algorithmes d’apprentissage automatique (ML) ont soulevé des préoccupations en matière de confidentialité e...