L'utilisation de modèles LSTM (Long Short-Term Memory) est une approche puissante pour prédire la consommation de puissance électrique, une série temporelle complexe influencée par de nombreux facteurs. Ce guide vous accompagnera étape par étape dans l'application d'un modèle LSTM, en exploitant vos données pré-traitées pour réaliser des prédictions précises à court terme (24 heures) et à plus long terme (1 semaine).
Vos données sont déjà normalisées et incluent 23 features pertinentes ainsi que les lag features (valeurs passées de la puissance électrique). C'est une excellente base. La colonne de date ('YYYY-MM-DD HH:MM:SS.ms') est également prête.
Exemple illustrant la relation entre la consommation d'énergie (kWh) et la température, soulignant l'importance des features externes.
Pour capturer efficacement les dépendances temporelles, notamment les cycles journaliers fréquents dans la consommation électrique (pics matinaux, creux nocturnes), l'utilisation des 24 dernières heures comme lags (soit 24 lags si vos données sont horaires) est un point de départ judicieux et couramment utilisé. Cela fournit au modèle une fenêtre temporelle d'une journée complète pour baser ses prédictions.
Bien que 24 lags soient une bonne heuristique, le nombre optimal peut varier. Des techniques comme l'analyse de la fonction d'autocorrélation (ACF) et d'autocorrélation partielle (PACF) de votre série temporelle cible, ou l'expérimentation via validation croisée, peuvent aider à affiner ce choix.
Même avec les lags, extraire des caractéristiques temporelles explicites de votre colonne 'date' peut améliorer les performances :
# Assumons que df est votre DataFrame pandas
df['date'] = pd.to_datetime(df['date'])
df['heure'] = df['date'].dt.hour
df['jour_semaine'] = df['date'].dt.dayofweek # Lundi=0, Dimanche=6
df['mois'] = df['date'].dt.month
df['jour_annee'] = df['date'].dt.dayofyear
# Ajoutez d'autres features pertinentes (ex: indicateur week-end/jour férié)
Les LSTM attendent des données d'entrée sous une forme 3D spécifique : `[nombre d'échantillons, nombre de pas de temps (lags), nombre de features]`. Vous devrez remodeler vos données d'entraînement et de test.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
# Supposons que 'df' contient vos données, 'target_column' est le nom de la colonne cible
# et 'feature_columns' est la liste des noms de toutes les colonnes de features (incluant lags et features temporelles)
target_column = 'puissance_electrique' # Remplacez par le nom réel
feature_columns = [col for col in df.columns if col not in ['date', target_column]]
X = df[feature_columns]
y = df[target_column]
# Division Train/Test (respectant l'ordre temporel)
test_size_ratio = 0.2 # 20% pour le test
train_size = int(len(df) * (1 - test_size_ratio))
X_train, X_test = X.iloc[:train_size], X.iloc[train_size:]
y_train, y_test = y.iloc[:train_size], y.iloc[train_size:]
# Fonction pour créer les séquences
def create_sequences(input_data, target_data, time_steps):
Xs, ys = [], []
for i in range(len(input_data) - time_steps):
Xs.append(input_data.iloc[i:(i + time_steps)].values)
ys.append(target_data.iloc[i + time_steps])
return np.array(Xs), np.array(ys)
TIME_STEPS = 24 # Nombre de lags/pas de temps
X_train_seq, y_train_seq = create_sequences(X_train, y_train, TIME_STEPS)
X_test_seq, y_test_seq = create_sequences(X_test, y_test, TIME_STEPS)
# Vérification des dimensions : X_train_seq.shape devrait être (samples, TIME_STEPS, num_features)
print(f"Dimensions X_train_seq: {X_train_seq.shape}")
print(f"Dimensions y_train_seq: {y_train_seq.shape}")
print(f"Dimensions X_test_seq: {X_test_seq.shape}")
print(f"Dimensions y_test_seq: {y_test_seq.shape}")
Problème potentiel : Assurez-vous que `X_train` et `X_test` contiennent bien toutes les features nécessaires, y compris les lags que vous avez préparés et les features temporelles extraites.
Solution : Vérifiez la liste `feature_columns` avant la création des séquences.
Nous allons construire un modèle LSTM simple mais efficace en utilisant Keras (via TensorFlow). Il comprendra une couche LSTM, une couche Dropout pour la régularisation (prévention du surapprentissage), et une couche Dense pour la sortie (prédiction de la puissance).
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.callbacks import EarlyStopping
# Définition du modèle
model = Sequential()
# Couche LSTM avec 50 unités. input_shape=(TIME_STEPS, nombre_de_features)
# return_sequences=True si vous empilez plusieurs couches LSTM
model.add(LSTM(units=50, activation='relu', input_shape=(X_train_seq.shape[1], X_train_seq.shape[2])))
# Couche Dropout pour réduire le surapprentissage
model.add(Dropout(0.2))
# Couche de sortie Dense avec 1 neurone (pour prédire une seule valeur)
model.add(Dense(units=1))
# Compilation du modèle
# 'adam' est un optimiseur courant. 'mse' (Mean Squared Error) est une fonction de perte adaptée à la régression.
model.compile(optimizer='adam', loss='mse', metrics=['mae']) # Ajout de MAE comme métrique suivie
model.summary() # Affiche un résumé de l'architecture du modèle
# Callback pour arrêter l'entraînement si la perte sur la validation ne s'améliore plus
# 'patience=10' signifie qu'on attend 10 époques sans amélioration avant d'arrêter.
early_stopping = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
# Entraînement du modèle
EPOCHS = 50 # Nombre maximal d'époques
BATCH_SIZE = 32 # Taille des lots utilisés pour chaque mise à jour des poids
history = model.fit(
X_train_seq, y_train_seq,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
validation_data=(X_test_seq, y_test_seq),
callbacks=[early_stopping],
verbose=1 # Affiche la progression
)
# L'objet 'history' contient l'historique des pertes et métriques pour chaque époque
Une fois le modèle entraîné, la première étape d'évaluation consiste à visualiser ses prédictions sur l'ensemble de test par rapport aux valeurs réelles.
import matplotlib.pyplot as plt
# Faire les prédictions sur l'ensemble de test
y_pred = model.predict(X_test_seq)
# Créer un index temporel pour l'axe des x du graphique
# Il faut ajuster l'index de y_test pour correspondre à y_test_seq
test_timestamps = df.iloc[train_size + TIME_STEPS:].index
# Tracer les courbes
plt.figure(figsize=(15, 7))
plt.plot(test_timestamps, y_test_seq, label='Valeurs Réelles', color='blue', linewidth=1)
plt.plot(test_timestamps, y_pred.flatten(), label='Prédictions LSTM', color='red', linestyle='--', linewidth=1)
plt.title('Comparaison Valeurs Réelles vs Prédictions LSTM sur l\'Ensemble de Test')
plt.xlabel('Temps')
plt.ylabel('Puissance Électrique (Normalisée)') # Ou unité d'origine si dénormalisé
plt.legend()
plt.grid(True)
plt.show()
Ce graphique permet d'identifier rapidement si le modèle capture les tendances générales, les pics, les creux, et s'il y a des retards ou des décalages systématiques dans les prédictions.
Complétons l'évaluation visuelle par des métriques quantitatives pour juger de la précision du modèle.
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# Calculer les métriques
# Note: y_pred est potentiellement en 2D (samples, 1), y_test_seq est 1D. Aplatir y_pred.
mse = mean_squared_error(y_test_seq, y_pred.flatten())
rmse = np.sqrt(mse) # Root Mean Squared Error, souvent plus interprétable
mae = mean_absolute_error(y_test_seq, y_pred.flatten())
r2 = r2_score(y_test_seq, y_pred.flatten())
print(f"Résultats sur l'ensemble de test :")
print(f" Mean Squared Error (MSE): {mse:.4f}")
print(f" Root Mean Squared Error (RMSE): {rmse:.4f}")
print(f" Mean Absolute Error (MAE): {mae:.4f}")
print(f" Coefficient de Détermination (R²): {r2:.4f}")
# Interprétation:
# - MSE/RMSE/MAE: Plus ils sont bas, mieux c'est. Le RMSE et le MAE sont dans la même unité que la cible.
# - R²: Proche de 1 indique que le modèle explique une grande partie de la variance des données. Proche de 0 signifie que le modèle n'est pas meilleur qu'une simple moyenne. Négatif signifie qu'il est pire.
L'objet `history` retourné par `model.fit()` contient les valeurs des métriques (loss, mae, val_loss, val_mae) à chaque époque. Les tracer permet de diagnostiquer le comportement de l'apprentissage.
# Tracer l'évolution des pertes (MSE) et MAE
plt.figure(figsize=(12, 5))
# Graphique de la Perte (MSE)
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='Perte Entraînement (MSE)')
plt.plot(history.history['val_loss'], label='Perte Validation (MSE)')
plt.title('Évolution de la Perte (MSE) par Époque')
plt.xlabel('Époque')
plt.ylabel('MSE')
plt.legend()
plt.grid(True)
# Graphique de la MAE
plt.subplot(1, 2, 2)
plt.plot(history.history['mae'], label='MAE Entraînement')
plt.plot(history.history['val_mae'], label='MAE Validation')
plt.title('Évolution de la MAE par Époque')
plt.xlabel('Époque')
plt.ylabel('MAE')
plt.legend()
plt.grid(True)
plt.tight_layout() # Ajuste l'espacement
plt.show()
# Analyse attendue:
# - Les courbes d'entraînement (MSE, MAE) doivent diminuer.
# - Les courbes de validation doivent également diminuer et suivre celles d'entraînement.
# - Si la validation commence à remonter ou stagne alors que l'entraînement continue de baisser -> Surapprentissage.
# - Si R² était ajouté comme métrique, on s'attendrait à ce qu'il augmente et se stabilise.
Le diagramme radar ci-dessous offre une vue synthétique de la performance du modèle évaluée par différentes métriques clés. Nous comparons ici les performances estimées sur les ensembles d'entraînement et de validation, ainsi que pour les prévisions à court terme (24h) et à long terme (1 semaine). Les valeurs de MAE et RMSE sont inversées et mises à l'échelle pour la visualisation (plus proche de 1 est meilleur), tandis que R² est utilisé directement (proche de 1 est meilleur).
Idéalement, les performances de validation devraient être proches de celles de l'entraînement. On s'attend à une dégradation progressive des métriques pour les prévisions à plus long terme (1 semaine) par rapport aux prévisions à court terme (24h) en raison de l'accumulation potentielle des erreurs.
L'objectif final est d'utiliser le modèle entraîné pour prédire des valeurs futures inconnues.
Pour prédire plusieurs pas de temps dans le futur, nous utilisons une approche itérative : 1. Prendre la dernière séquence de données réelles connues (la fin de l'ensemble de test). 2. Prédire la valeur pour le prochain pas de temps. 3. Mettre à jour la séquence d'entrée en supprimant le pas de temps le plus ancien et en ajoutant la valeur prédite (et en mettant à jour les features externes si possible, comme la météo prévue). 4. Répéter les étapes 2 et 3 pour le nombre de pas de temps désiré (24 pour 24h, 168 pour 1 semaine).
def iterative_forecast(model, initial_sequence, steps, num_features, target_index):
"""
Réalise une prédiction itérative.
- model: Le modèle LSTM entraîné.
- initial_sequence: La dernière séquence de données réelles connues (shape: 1, TIME_STEPS, num_features).
- steps: Le nombre de pas de temps à prédire.
- num_features: Le nombre total de features dans la séquence.
- target_index: L'index de la colonne cible (puissance) dans les features.
(Important si les lags sont inclus dans les features à mettre à jour).
Si les lags ne sont PAS parmi les features mais gérés séparément, ajuster la logique.
Supposons ici que la cible prédite doit mettre à jour une des features (ex: le lag le plus récent).
"""
forecast = []
current_sequence = initial_sequence.copy()
for _ in range(steps):
# 1. Prédire le prochain pas de temps
predicted_value = model.predict(current_sequence)[0, 0]
forecast.append(predicted_value)
# 2. Préparer la nouvelle feature pour le prochain pas
# !!! ATTENTION: Mise à jour simpliste !!!
# Ici, on suppose que la prédiction remplace une feature (ex: le lag_1 si inclus).
# Les autres features (météo, temps) devraient idéalement être mises à jour
# avec leurs valeurs prévues pour ce futur pas de temps.
# C'est une simplification majeure. Une vraie implémentation nécessiterait
# des prévisions pour les features exogènes.
new_features_step = current_sequence[0, -1, :].copy() # Copie le dernier pas de temps connu
# Met à jour la feature cible/lag (si applicable et son index connu)
# new_features_step[target_index] = predicted_value # Exemple: mettre à jour le lag 1 si c'est une feature
# Créer le nouveau pas de temps complet (potentiellement avec features exogènes mises à jour)
# Si les autres features ne sont pas mises à jour, on répète les dernières connues:
new_step_full_features = new_features_step # Placeholder - idéalement, mettre à jour toutes les features ici
# 3. Mettre à jour la séquence: supprimer le plus ancien, ajouter le nouveau
new_sequence_data = np.vstack((current_sequence[0, 1:, :], new_step_full_features))
current_sequence = np.expand_dims(new_sequence_data, axis=0)
return np.array(forecast)
# Préparer la dernière séquence de l'ensemble de test
last_known_sequence = np.expand_dims(X_test_seq[-1], axis=0)
num_features = X_test_seq.shape[2]
# !!! Définir l'index correct de la colonne cible/lag si elle fait partie des features
target_feature_index = -1 # Exemple: si la cible elle-même (ou lag_1) est la dernière feature
# Prédiction pour 24 heures
forecast_24h = iterative_forecast(model, last_known_sequence, 24, num_features, target_feature_index)
# Prédiction pour 1 semaine (168 heures)
# Attention: la qualité peut se dégrader fortement
forecast_1w = iterative_forecast(model, last_known_sequence, 168, num_features, target_feature_index)
print(f"Prévisions pour les 24 prochaines heures:\n{forecast_24h}")
print(f"Prévisions pour la semaine suivante:\n{forecast_1w}")
Si vous disposez des données réelles pour la période que vous avez prédite, vous pouvez calculer les métriques d'erreur pour évaluer la performance réelle des prévisions.
# Supposons que 'y_future_real_24h' et 'y_future_real_1w' contiennent les vraies valeurs
# pour les périodes prédites.
try:
# Remplacer par vos vraies données futures si disponibles
# y_future_real_24h = ... # Charger les 24 vraies valeurs suivantes
# y_future_real_1w = ... # Charger les 168 vraies valeurs suivantes
if 'y_future_real_24h' in locals():
mse_24h = mean_squared_error(y_future_real_24h, forecast_24h)
mae_24h = mean_absolute_error(y_future_real_24h, forecast_24h)
r2_24h = r2_score(y_future_real_24h, forecast_24h)
print("\nMétriques pour la prévision 24h:")
print(f" MSE: {mse_24h:.4f}, RMSE: {np.sqrt(mse_24h):.4f}, MAE: {mae_24h:.4f}, R²: {r2_24h:.4f}")
if 'y_future_real_1w' in locals():
mse_1w = mean_squared_error(y_future_real_1w, forecast_1w)
mae_1w = mean_absolute_error(y_future_real_1w, forecast_1w)
r2_1w = r2_score(y_future_real_1w, forecast_1w)
print("\nMétriques pour la prévision 1 semaine:")
print(f" MSE: {mse_1w:.4f}, RMSE: {np.sqrt(mse_1w):.4f}, MAE: {mae_1w:.4f}, R²: {r2_1w:.4f}")
except NameError:
print("\nVariables 'y_future_real_24h' ou 'y_future_real_1w' non définies. Impossible de calculer les erreurs futures.")
Problème Potentiel : Accumulation d'erreur. Chaque erreur dans une prédiction se propage aux suivantes, dégradant la qualité sur les horizons longs.
Solutions : Modèles multi-sorties (prédisant plusieurs pas directement), ré-entraînement fréquent avec de nouvelles données, amélioration des features (notamment prévisions météo fiables), utilisation de modèles plus complexes (ex: Seq2Seq avec Attention).
Cette vidéo (en anglais) illustre l'utilisation des réseaux de neurones récurrents LSTM pour la prévision de la consommation électrique domestique. Elle montre concrètement comment traiter les données temporelles, construire un modèle LSTM et l'entraîner pour prédire la consommation future, ce qui est très similaire à l'approche décrite dans ce guide. Regarder cette vidéo peut vous donner des aperçus visuels et pratiques sur l'implémentation.
Ce diagramme mental résume le flux de travail typique pour la prévision de séries temporelles avec LSTM, depuis la préparation initiale jusqu'à la prédiction finale.
Le tableau suivant résume certains hyperparamètres clés du modèle LSTM discuté et donne une idée des ordres de grandeur typiques pour les métriques de performance sur un problème de prévision de consommation électrique (les valeurs réelles dépendront fortement de vos données spécifiques et de l'optimisation).
Paramètre / Métrique | Valeur / Type Suggéré | Description | Résultat Typique Attendu (Validation/Test) |
---|---|---|---|
Nombre de Lags (Time Steps) | 24 (pour données horaires) | Fenêtre temporelle utilisée comme entrée. | N/A (Hyperparamètre) |
Unités LSTM | 50 - 100 | Nombre de neurones dans la couche LSTM. | N/A (Hyperparamètre) |
Taux de Dropout | 0.2 - 0.5 | Fraction des unités à désactiver pour régulariser. | N/A (Hyperparamètre) |
Optimiseur | Adam | Algorithme de mise à jour des poids. | N/A (Hyperparamètre) |
Fonction de Perte | MSE (Mean Squared Error) | Mesure l'erreur pendant l'entraînement. | Minimisée |
Époques | 50-200 (avec Early Stopping) | Nombre de passages sur l'ensemble d'entraînement. | N/A (Hyperparamètre) |
Taille de Lot (Batch Size) | 32 - 128 | Nombre d'échantillons par mise à jour des poids. | N/A (Hyperparamètre) |
R² (Coefficient de Détermination) | Métriques d'évaluation | Proportion de variance expliquée. | > 0.9 (très bon), > 0.8 (bon) |
MAE (Mean Absolute Error) | Métriques d'évaluation | Erreur moyenne absolue (unité de la cible). | Aussi bas que possible |
RMSE (Root Mean Squared Error) | Métriques d'évaluation | Erreur quadratique moyenne racine (unité de la cible). | Aussi bas que possible |