Deep Learning: comprendre et construire des réseaux de neurones

Auteur: Mohamed CHINY Durée necessaire pour le cours de Deep Learning: comprendre et construire des réseaux de neurones Niveau recommandé pour le cours de Deep Learning: comprendre et construire des réseaux de neurones Supports vidéo non disponibles pour ce cours Exercices de renforcement non disponibles pour ce cours Quiz non disponibles pour ce cours

Leçon 16: Apprendre à construire un RNN simple pour la prédiction séquentielle

Toutes les leçons

Prédire la suite d’une séquence numérique avec Keras et SimpleRNN

Implémenter un réseau de neurones reccurrents (RNN)

Comme on l'a vu dans le leçon précédente, les réseaux récurrents (RNN) permettent de traiter des données séquentielles en tenant compte du contexte des éléments précédents. Après avoir étudié leur fonctionnement théorique, il est essentiel de passer à une mise en pratique pour consolider la compréhension.

Dans cette leçon, nous utilisons Keras et la couche SimpleRNN pour construire un modèle capable de prédire le prochain élément d’une suite numérique. L’objectif est de montrer comment un réseau récurrent apprend à anticiper la continuité d’une séquence. Donc, nous allons créer un dataset fictif constitué simplement d’une suite numérique allant de 0 à 99 afin de disposer d’un exemple clair et minimaliste pour entraîner notre modèle.

Cette fois, je vais vous montrer le code complet, puis détailler pas à pas chacun des blocs pour en expliquer le rôle:
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, SimpleRNN, Dense

# Préparation des données (Séquence de base : 0 à 99)
data = np.arange(100)

# Création des séquences (X) et des cibles (y)
seq_length = 5 # longueur de la fenêtre
X, y = [], []
for i in range(len(data) - seq_length):
   X.append(data[i:i+seq_length])
   y.append(data[i+seq_length])

X = np.array(X)
y = np.array(y)

# Reshape pour RNN : (samples, timesteps, features)
X = X.reshape((X.shape[0], X.shape[1], 1))

# 2. Construction du modèle
model = Sequential([
   Input(shape=(seq_length, 1)),
   SimpleRNN(50, activation='tanh'),
   Dense(1)
])
model.compile(optimizer='adam', loss='mse')

# 3. Entraînement
model.fit(X, y, epochs=200, verbose=0)

# 4. Test du modèle
test_seq = np.array([10,11,12,13,14]).reshape((1, seq_length, 1))
pred = model.predict(test_seq, verbose=0)
print("Séquence :", [10,11,12,13,14])
print("Prédiction du prochain nombre :", pred[0][0])

Explication du code pas-à-pas avec la logique de la fenêtre glissante

On commence par importer les modules nécessaires à notre projet:
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, SimpleRNN, Dense
La couche SimpleRNN est un module de Keras/TensorFlow situé dans keras.layers. Elle implémente un réseau récurrent simple entièrement connecté où la sortie à chaque timestep est réinjectée comme entrée au timestep suivant.

Ensuite, on génère un dataset fictif constitué d’une suite numérique allant de 0 à 99, puis nous réorganisons ces données afin qu’elles reflètent la notion de séquence grâce à l’application d’une fenêtre glissante.
data = np.arange(100)

seq_length = 5
X, y = [], []
for i in range(len(data) - seq_length):
   X.append(data[i:i+seq_length])
   y.append(data[i+seq_length])

X = np.array(X)
y = np.array(y)
On définit la taille de la fenêtre (seq_length=5), c’est-à-dire le nombre de timesteps que le modèle va utiliser comme contexte pour prédire la suite.

Le code précédent met en œuvre la logique de la fenêtre glissante, une technique essentielle pour transformer une suite continue en données exploitables par un RNN.

L’idée derrière la fenêtre glissatne consiste à prendre, à chaque itération, une sous-séquence de longueur fixe (seq_length = 5) comme entrée (X) et d’associer à cette fenêtre le nombre qui la suit immédiatement comme cible (y). Ainsi, [0,1,2,3,4] → 5, puis [1,2,3,4,5] → 6, etc.
La fenêtre glissante est utile car elle permet de générer de nombreux exemples supervisés à partir d’une seule série, en donnant au modèle un contexte temporel limité mais suffisant pour apprendre à prédire le prochain élément. Autrement dit, elle convertit une séquence brute en couples (entrée, sortie) qui capturent la dynamique des timesteps successifs.

La prochaine étape consiste à adapter la forme des données pour qu’elles soient compatibles avec l’entrée attendue par un RNN dans Keras.
X = X.reshape((X.shape[0], X.shape[1], 1))
Initialement, chaque séquence est un tableau 2D (nombre d’exemples × longueur de la séquence), mais un RNN exige une structure en trois dimensions: (samples, timesteps, features).

Ici, X.shape[0] correspond au nombre total d’exemples, X.shape[1] au nombre de timesteps (longueur de la fenêtre glissante), et 1 indique qu’à chaque timestep on fournit une seule caractéristique (feature).
Dans des cas plus complexes (notamment en NLP) on peut fournir plusieurs caractéristiques par timestep, comme les embeddings d’un mot (vecteurs multidimensionnels qui capturent son sens et son contexte). Nous verrons cela en détail dans le cours dédié au traitement du langage naturel.
Cette transformation est donc indispensable car elle permet au réseau de traiter chaque fenêtre glissante comme une séquence temporelle où chaque pas (timestep) contient une donnée élémentaire.

Maintenant, on définit l'architecture de notre modèle:
model = Sequential([
   Input(shape=(seq_length, 1)),
   SimpleRNN(50, activation='tanh'),
   Dense(1)
])
model.compile(optimizer='adam', loss='mse')
Ce code définit un modèle séquentiel composé d’une couche d’entrée qui précise la forme des données (seq_length timesteps avec une seule feature par timestep), suivie d’une couche SimpleRNN de 50 unités utilisant l’activation tanh pour capturer les dépendances temporelles, puis d’une couche Dense finale qui produit la prédiction du prochain élément de la séquence.

Dans la compilation du modèle, on indique que l’on prépare ce dernier pour une tâche de régression. En effet, la fonction de perte mse (Mean Squared Error) mesure l’écart entre les valeurs prédites et les valeurs réelles. Comme notre objectif est de prédire un nombre (le prochain élément d’une séquence) plutôt qu’une classe, on utilise une fonction de perte adaptée à la régression, ce qui permet au RNN d’apprendre à minimiser les erreurs numériques dans ses prédictions.

On entraine notre modèle:
model.fit(X, y, epochs=200, verbose=0)
Le modèle est entrainé sur les données X et y pendant 200 époques. Le paramètre verbose=0 signifie que l’entraînement se fait en silence, sans affichage des détails à chaque époque. Le nombre d’époques est ajustable. Donc, on peut l’augmenter ou le réduire selon la complexité du problème et la vitesse de convergence.
Il est recommandé de fixer le nombre d'époques en s’appuyant sur une courbe d’apprentissage (ou learning curve), qui permet de visualiser l’évolution de la perte et de déterminer le moment optimal où le modèle cesse de progresser.
La dernière étape consiste à tester notre modèle:
test_seq = np.array([10,11,12,13,14]).reshape((1, seq_length, 1))
pred = model.predict(test_seq, verbose=0)
print("Séquence :", [10,11,12,13,14])
print("Prédiction du prochain nombre :", pred[0][0])
Ce bloc de code illustre la phase de test du modèle où on construit une séquence fictive [10,11,12,13,14] et on la redimensionne en (1, seq_length, 1) pour respecter le format attendu par le RNN (une seule séquence, composée de 5 timesteps, chacun avec une seule feature). On passe ensuite cette séquence au modèle via la méthode predict qui génère une estimation du prochain nombre.

Après exécution, on obtient ce résultat:
Séquence : [10, 11, 12, 13, 14]
Prédiction du prochain nombre : 14.956546
La suite logique de la séquence [10,11,12,13,14] correspond bien à 15, et le modèle a prédit une valeur très proche qui est 14.96. Cela montre que le réseau a effectivement appris à prolonger la suite numérique. En effet, grâce à la fenêtre glissante, il exploite les timesteps précédents pour anticiper le suivant, et grâce à l’entraînement en régression, il fournit une estimation continue qui s’approche de la valeur attendue.

En conclusion, cet exemple illustre bien la puissance des réseaux récurrents (RNN). Ces réseaux sont capables de capter la dynamique temporelle d’une séquence et de prolonger logiquement une suite numérique grâce à la fenêtre glissante et à l’entraînement en régression.

Toutefois, les RNN classiques restent limités par le problème du vanishing gradient qui rend difficile l’apprentissage de dépendances à long terme. C’est pourquoi, dans les prochaines leçons, nous explorerons des architectures plus avancées comme les LSTM et les GRU, conçues pour surmonter cette difficulté et offrir une meilleure performance sur des séquences plus complexes.