Module 1 — Ce qui rend une séquence différente d'un tableau de variables
Le cours 07 traitait chaque exemple comme une ligne indépendante d'un tableau. Une séquence casse cette hypothèse : l'ordre porte de l'information, la longueur varie d'un exemple à l'autre, et deux valeurs voisines ne sont pas indépendantes. Ce module montre pourquoi un modèle dense y échoue sans prévenir, et comment préparer une série sans se mentir sur ses résultats.
Le fil rouge : la consommation électrique d'un bâtiment
Pendant tout le cours, nous travaillons sur la consommation horaire d'un bâtiment tertiaire, mesurée en kilowattheures. Les données arrivent une valeur par heure, sur plusieurs années, avec quelques variables auxiliaires : jour de la semaine, indicateur de jour férié, température extérieure. La tâche est simple à énoncer : à partir des 168 dernières heures (une semaine), prévoir les 24 heures qui suivent. Elle est difficile à faire correctement, parce que la moindre étourderie sur le découpage rend un résultat impossible à reproduire en production.
Ordre, dépendance et longueur variable
Trois propriétés distinguent une séquence d'un tableau classique.
L'ordre porte du sens. Permuter les colonnes d'un tableau de clients n'affecte pas la prédiction ; permuter deux jours d'une série de consommation la rend incompréhensible. Un modèle dense qui reçoit une fenêtre aplatie ne sait pas quelle position est ancienne et laquelle est récente.
Les valeurs sont dépendantes. La consommation à midi ressemble à celle d'hier midi, à celle de la veille à 11 h, et anticipe celle de 13 h. Cette autocorrélation est ce que le modèle doit apprendre à exploiter, mais c'est aussi ce qui interdit d'échantillonner au hasard : deux exemples proches dans le temps sont pratiquement le même exemple.
La longueur varie. Une phrase française n'a pas le même nombre de mots qu'une autre. Un capteur peut manquer un pas. Un modèle dense demande une entrée de taille fixe et n'a aucun mécanisme pour traiter la variable « nombre de pas ». Le module 9 y consacre le remplissage et le masquage.
Pourquoi le modèle dense échoue
L'intuition première est de coller les 168 heures les unes à côté des autres et de passer le vecteur résultant à un réseau dense classique. On observe alors trois symptômes.
D'abord, le modèle surapprend brutalement. Chaque position devient une variable à part entière : heures avec 3 variables donnent entrées sans structure. Un réseau dense n'apprend pas que la position 24 signifie « hier même heure », il apprend un poids indépendant par position.
Ensuite, il ne généralise pas à des séries plus longues. Une fenêtre de heures exige une nouvelle architecture et un nouvel entraînement, alors que la structure temporelle est la même.
Enfin, il ignore les patrons partagés. Le motif « une descente lente puis une remontée brutale » a la même signification à 8 h ou à 20 h, mais un dense ne le reconnaît qu'à la position où il l'a vu à l'entraînement.
Le neurone récurrent du module 2 répond aux trois : mêmes poids à tous les pas, traitement séquentiel, longueur libre.
Fenêtres glissantes : le format d'entrée d'un récurrent
Un réseau récurrent attend un tenseur de forme : exemples, chacun de pas de temps, chacun décrit par variables. Construire ces fenêtres à partir d'une série consiste à parcourir le vecteur et à découper des tranches.
import numpy as np
def fenetrer(serie, longueur_entree=168, longueur_cible=24, pas=1):
"""Découpe une série 1D en fenêtres (entrée, cible)."""
X, y = [], []
limite = len(serie) - longueur_entree - longueur_cible + 1
for i in range(0, limite, pas):
X.append(serie[i : i + longueur_entree])
y.append(serie[i + longueur_entree : i + longueur_entree + longueur_cible])
return np.array(X), np.array(y)
serie = np.load("consommation.npy") # forme (n_heures,)
X, y = fenetrer(serie, 168, 24, pas=1)
X = X[..., None] # forme (n_fenetres, 168, 1)
print(X.shape, y.shape)
Trois choix reviennent partout. La longueur d'entrée couvre au moins un cycle complet du phénomène : ici une semaine, pour attraper à la fois le rythme quotidien et la différence semaine-fin de semaine. La longueur de cible répond à un besoin métier, pas à une élégance d'architecture. Le pas entre deux fenêtres contrôle le nombre d'exemples : un pas de 1 heure donne un jeu très corrélé mais très grand ; un pas de 24 heures donne des exemples plus indépendants mais dix fois moins nombreux.
La fuite temporelle : le piège qui invalide tout
C'est l'erreur la plus grave du cours, celle qui donne des scores flatteurs sur les diapositives et catastrophiques en production. La fuite temporelle survient dès qu'une information du futur se glisse dans les données d'entraînement d'un exemple qui devrait ne connaître que le passé.
Elle apparaît sous trois formes.
Le mélange aléatoire avant découpage. Un train_test_split avec
shuffle=True prélève des exemples au hasard : l'entraînement contient des
fenêtres qui suivent chronologiquement des fenêtres de test. Le modèle apprend
en pratique à interpoler entre deux voisines connues.
La normalisation globale. Calculer la moyenne et l'écart-type sur l'ensemble de la série avant de séparer entraînement et test transmet au modèle des statistiques qui incluent le test. Le module 9 reprend ce point avec la normalisation par fenêtre.
Les variables tirées du futur. Ajouter comme variable auxiliaire une « moyenne des 24 heures suivantes » ferme la boucle : le modèle prédit ce qu'on lui donne déjà.
Le bon découpage est strictement chronologique : premiers 70 % pour l'entraînement, 15 % suivants pour la validation, 15 % derniers pour le test.
n = len(X)
i_train = int(0.70 * n)
i_val = int(0.85 * n)
X_train, y_train = X[:i_train], y[:i_train]
X_val, y_val = X[i_train:i_val], y[i_train:i_val]
X_test, y_test = X[i_val:], y[i_val:]
Le modèle s'entraîne, la validation diminue, le test paraît excellent. Rien n'indique le problème avant la mise en production, où les scores s'effondrent. La seule protection est méthodologique : découper avant tout traitement, ne calculer les statistiques que sur l'entraînement, ne jamais mélanger avant de diviser. C'est reformulé au module 9 et vérifié au module 10.
Avant de célébrer un score, comparez au prédicteur qui recopie la valeur d'il y a 24 heures (ou d'il y a une semaine). Sur une série saisonnière, cette référence est étonnamment forte. Un modèle qui ne la bat pas nettement ne sert à rien, et un modèle qui la bat massivement du premier coup cache généralement une fuite. Le module 10 en fait un critère systématique.
En résumé
- Une séquence porte l'ordre, la dépendance et la longueur variable : ces trois propriétés cassent les hypothèses d'un réseau dense classique.
- L'entrée d'un récurrent est un tenseur ; on le construit avec des fenêtres glissantes dont on choisit la longueur, la cible et le pas.
- La fuite temporelle apparaît dès qu'une information du futur se retrouve dans l'entraînement : mélange aléatoire, normalisation globale, variable future ; le seul remède est un découpage strictement chronologique.
- Une référence naïve (recopie de la valeur passée) est le seul juge honnête d'un score : la battre est le vrai critère d'utilité.
Module suivant : le neurone récurrent, l'objet qui rend possible le traitement pas à pas d'une séquence sans dupliquer les poids.