Aller au contenu principal

Module 5 — LSTM : portes d'oubli, d'entrée et de sortie

Le module 4 a diagnostiqué la disparition du gradient dans un RNN simple. Le LSTM (Long Short-Term Memory) y répond par une idée simple : ajouter un chemin où le gradient peut circuler sans être multiplié à chaque pas. Ce chemin s'appelle l'état de cellule, et il est régulé par trois portes.

Deux états au lieu d'un

Un LSTM maintient deux vecteurs par pas, tous deux de dimension HH :

  • L'état de cellule ctc_t : la mémoire longue, protégée par les portes.
  • L'état caché hth_t : la sortie visible, produite à partir de ctc_t.

Cette distinction est le cœur du modèle. L'état de cellule circule presque sans transformation d'un pas à l'autre : il est modifié par une addition, pas par une multiplication matricielle répétée. C'est ce qui permet au gradient de le remonter sur des centaines de pas sans s'écraser.

Les trois portes

Chaque porte est un vecteur de dimension HH, calculé à partir de xtx_t et ht1h_{t-1} par une couche dense suivie d'une sigmoïde. La sortie est comprise entre 0 et 1, coordonnée par coordonnée, et joue le rôle d'un filtre : 0 « ferme », 1 « laisse passer ».

Porte d'oubli ftf_t : décide, pour chaque coordonnée de ct1c_{t-1}, ce qui est conservé.

ft=σ(Wf[ht1,xt]+bf)f_t = \sigma(W_f [h_{t-1}, x_t] + b_f)

Porte d'entrée iti_t : décide, pour chaque coordonnée d'un candidat c~t\tilde{c}_t, ce qui est écrit.

it=σ(Wi[ht1,xt]+bi),c~t=tanh(Wc[ht1,xt]+bc)i_t = \sigma(W_i [h_{t-1}, x_t] + b_i), \quad \tilde{c}_t = \tanh(W_c [h_{t-1}, x_t] + b_c)

Porte de sortie oto_t : décide, pour chaque coordonnée de ctc_t, ce qui apparaît dans hth_t.

ot=σ(Wo[ht1,xt]+bo)o_t = \sigma(W_o [h_{t-1}, x_t] + b_o)

La mise à jour, en deux lignes

Avec les portes, la mise à jour se réduit à :

ct=ftct1+itc~tc_t = f_t \odot c_{t-1} + i_t \odot \tilde{c}_t ht=ottanh(ct)h_t = o_t \odot \tanh(c_t)

\odot est le produit terme à terme. La ligne clé est la première : l'état de cellule évolue par une addition au terme précédent, et non par une multiplication par une matrice de récurrence. Le gradient qui remonte cTc_T vers ctc_t traverse essentiellement des multiplications par fkf_k, compris entre 0 et 1 mais qui peuvent rester proches de 1 pendant très longtemps si la porte apprend à conserver.

C'est ce chemin qui rend les dépendances longues apprenables. Un RNN simple doit multiplier par WhW_h à chaque pas ; un LSTM peut, s'il l'apprend, garder ft=1f_t = 1 et transmettre ctc_t tel quel.

Compter les paramètres

Chaque porte est une couche dense de HH sorties qui prend en entrée la concaténation de ht1h_{t-1} (taille HH) et de xtx_t (taille FF). Elle a donc H(H+F)+HH \cdot (H + F) + H paramètres. Il y a quatre transformations affines de cette forme (trois portes plus le candidat c~t\tilde{c}_t).

parameˋtres LSTM=4(H(H+F)+H)=4H(H+F+1)\text{paramètres LSTM} = 4 \cdot (H \cdot (H + F) + H) = 4H(H + F + 1)

Pour la consommation électrique avec F=1F = 1 et H=64H = 64 : 464(64+1+1)=168964 \cdot 64 \cdot (64 + 1 + 1) = 16\,896. Un RNN simple à la même configuration a 42244224 paramètres. Le LSTM coûte environ quatre fois plus, pour une capacité qualitativement différente.

LSTM en Keras

Le remplacement d'un SimpleRNN par un LSTM est mécanique. C'est le premier réflexe dès qu'une tâche a des dépendances au-delà d'une vingtaine de pas.

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

modele = keras.Sequential([
layers.Input(shape=(168, 1)), # 168 heures d'entree
layers.LSTM(64), # une seule couche LSTM
layers.Dense(24), # 24 heures de prevision
])

modele.compile(
optimizer=keras.optimizers.Adam(learning_rate=1e-3, clipnorm=1.0),
loss="mse",
metrics=["mae"],
)

historique = modele.fit(
X_train, y_train,
validation_data=(X_val, y_val),
epochs=50,
batch_size=64,
callbacks=[keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True)],
)

Deux appels utiles à la mise au point. return_state=True renvoie hTh_T et cTc_T séparément, utile pour un encodeur-décodeur (module 8). stateful=True conserve cc et hh d'un lot au suivant, à combiner avec reset_states() (module 3).

Lire les portes pour comprendre

Contrairement au RNN simple, les portes d'un LSTM sont interprétables : ce sont des vecteurs entre 0 et 1 dont on peut suivre l'activation moyenne. Extraire les portes demande un peu de tuyauterie, mais donne des diagnostics lisibles.

  • Une porte d'oubli dont la valeur moyenne est proche de 0 signale un modèle qui « oublie tout tout de suite » : mémoire courte, indistinguable d'un dense fenêtre.
  • Une porte d'entrée dont la valeur moyenne est proche de 0 signale un modèle qui n'écrit rien : la mémoire reste figée à zéro.
  • Un couple avec f1f \approx 1 et i0i \approx 0 pendant plusieurs pas signale un modèle qui maintient activement un signal ancien. C'est le comportement recherché sur une série saisonnière.

Une variante à connaître : unit_forget_bias

Un détail d'initialisation change beaucoup la vitesse de convergence : le biais de la porte d'oubli est initialisé à 1 (au lieu de 0) par défaut en Keras. La porte démarre donc « ouverte », et le modèle apprend à fermer là où il faut, plutôt que d'apprendre à ouvrir depuis un état par défaut fermé.

L'effet est particulièrement visible sur les premières époques : sans ce biais, la mémoire s'écrase à zéro dès le pas 5 ou 10 et le gradient disparaît malgré l'architecture. Le laisser tel quel est ce qu'il faut faire dans 99 % des cas.

Un LSTM est lent : la boucle n'est pas parallélisable

Le temps par époque d'un LSTM sur une même longueur TT est typiquement 2 à 3 fois celui d'un SimpleRNN, à cause des quatre transformations affines par pas. Ce coût est fixe : ajouter des accélérateurs distribue les exemples mais n'accélère pas un exemple. Si la latence par pas devient critique en production, CuDNNLSTM (fusion CUDA en Keras/TensorFlow) coupe souvent le temps par deux, mais uniquement avec des activations par défaut et sans masquage.

LSTM ou GRU ?

Le module 6 traite la question en profondeur. En attendant : sur une tâche courte à moyenne (moins de 100 pas), les deux se valent en résultat, et le GRU est plus rapide. Le LSTM garde un avantage sur les dépendances très longues et sur les tâches où l'état de cellule séparé apporte de la stabilité (langage, code source). Sur la consommation électrique de 168 h, les deux sont acceptables ; on comparera sur le fil rouge au module 10.

En résumé

  • Un LSTM sépare état de cellule ctc_t (mémoire longue, protégée) et état caché hth_t (sortie visible), et régule la mise à jour par trois portes sigmoïdes.
  • La mise à jour de ctc_t est une addition pondérée par les portes, pas une multiplication matricielle répétée : c'est ce qui permet au gradient de circuler sur de longues séquences.
  • Le nombre de paramètres est quatre fois celui d'un RNN simple de même HH ; le coût en temps par pas est environ deux à trois fois supérieur.
  • Le biais d'oubli initialisé à 1 est un défaut Keras à laisser tel quel ; le changer ralentit dramatiquement la convergence sans bénéfice.

Module suivant : le GRU rassemble deux des trois portes du LSTM en une, avec un compromis coût-résultat souvent gagnant.