Introduction : Un premier réseaux de neurones RNN
Nous avons vu dans les TDs précédents que les réseaux de neurones sont très performants pour détecter des motifs dans des données.
Pour des données qui ne sont pas ordonnées, nous avons expérimenté avec des réseau de neurones pleinement connectées (FC pour fully-connected, ou FCNNN pour fully-connected neural network).
Pour des données qui ont des caractéristiques spatiales et locales, comme des pixels dans des images, nous avons vu que les CNN (convolutional neural networks), donc les réseau de neurones convolutionnel apprennent des caractéristiques en apprenant des filtres qui sont appliquées sur des images.
Pour des données dont nous connaissons par à priori leurs ressemblences, nous avons travaillé sur des Autoencoders, qui permettent de trouver des proximités entre des données dans un espace à dimension inférieur (appelé espace latent ou bottleneck).
Dans ce TD, nous allons travaillé sur d’autres formes de données ordonnées, les séquences, où une donnée dépend directement des données précédentes. Nous trouvons des séquences dans diverses applications. Par exemple, pour la prédiction de séries temporelles (time series), tel que des prédiction de températures en considérent les températures d’une période de temps précédents, ou le taux d’échange d’une devise en se basant sur son historique d’évolution de taux. Une autre application est la génération de texte : si on considère que chaque mot dans un texte est une donnée, on pourrait imaginer de prédire le prochain mot de la séquence. Une autre application et de prédire les séquences DNA, par exemple.
Les RNN (recurrent neural networks), ou réseau de neurones récurrents, ont été introduit afin de capter des motifs dans des données séquentielles.
Au coeur de ce réseau de neurones est une couche récurrentes, qui garde un état interne (hidden state) dans un nombre de neurones défini (hidden units), et qui considère un nombre fini d’état antérieurs.
Pour cela, vous pouvez considérer le diagramme suivant :
Il montre comment une couche récurrente est déroulé (unfolded) sur \(t\) timesteps, faisant apparaître les états internes \(h_i\). L’état initial \(h_0\) est généralement intialisé avec des \(0\).
Pour faire apparaître les nombre de neurones \(m\) dans les couches RNN, considerez le diagramme suivant avec \(m=3\) :
Notez que la couche de sortie est ici une couche de type fully-connected, et elle contient juste une sortie (bien sûr, on pourrait avoir plusieurs sorties).
Le réseau de neurones ci-dessus fait donc apparaître pour la couche RNN
- \(m\) poids pour l’entrée via la couche cachée
- \(m*m\) poids et \(m\) biais entre l’état interne et la couche cachée,
ainsi que pour la couche de sortie :
- \(m\) poids et \(1\) biais.
Nous allons utiliser les RNN dans un contexte d’apprentissage supervisé (supervised learning).
Nous reconstruisons donc de nouveau une fonction \[ f(X) = y \]
Dans ce TD, nous allons avoir \(X \in R^{t}\) avec \(t\) le nombre de time steps à considérer.
Le premier objectif de ce TD est de comprendre le principe de l’architecture des RNN.
Le deuxième objectif est d’expérimenter avec une série temporelles synthétique, sous forme de courbe sinusoidales, et dont nous connaissons donc très bien son comportement dans le futur.
Le troisième objectif est de travailler avec une série temporelle du monde réel, tel que la prédiction de taux d’échange entre, par exemple, EURO et USD.
Voici le plan des exercices
un premier modèle RNN pour de données synthétisées pour tout bien comprendre.
un second modèle RNN qui utilisent des données réelles.
Les deux exercices sont de type régression, c’est à dire nous allons prédire des valeurs.
Exercice 5.0.A : Préparation de l’environnement JupyterHub
Nous allons maintenant travailler sur le JupyterHub comme dans les derniers TDs. Avec votre navigateur préféré, ouvrez l’URL suivant et authentifiez-vous :
https://jupyterhub.emi.u-bordeaux.fr/
Sur votre espace, vous avez déjà créée un dossier s’appelant deeplearning. Dans ce dossier, créez un sous-dossier td5. Dans ce sous-dossier, créez un nouveau notebook sous le nom td5-rnn.ipynb.
Nous allons importer maintenant toutes les bibliothèques dont on aura besoin pour ce TD. Pour cela, créez une nouvelle cellule Code dans votre notebook, mettez le code suivant et exécutez.
# calcul numérique et analyse de données
import pandas as pd
import numpy as np
# plotting
import matplotlib.pyplot as plt
# tensorflow keras
from tensorflow import keras
from keras import layers
from keras.layers import Dense, SimpleRNN
from keras.layers import Conv2D
from keras.models import Sequential
from keras.optimizers import Adam
import time # pour créer des noms de fichiers en fonction du tempsVérifiez qu’il n’y ait pas d’erreurs, sinon faites signe au plus vite.
Exercice 5.1 : Un premier RNN pour prédire une courbe sinusoidale
Créez une nouvelle cellule Markdown dans votre notebook, et ajouter le titre suivant, et exécutez la cellule :
# 1. Créer des données synthétiques
Pour cet exercice, nous allons créer des données synthétiques d’une courbe sinusoidale avec \(N=100\) données. De nouveau, on utilise un facteur de bruit qu’on initialise à 0 pour l’instant.
Créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant et exécutez la cellule :
N = 100 # Nombre de donnnées
noise_factor= 0.0
t=np.arange(0,N)
# création de données
x=np.sin(0.2*t) + noise_factor*np.random.rand(N)
# normaliser pour simplifier et être entre [0, 1]
x=(x+1)/2
df = pd.DataFrame(x)
df.head(20)Vous pouvez maintenant facilement visualiser ces données : créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant et exécutez la cellule :
# Visualiser la série de données
plt.plot(df)
plt.show() Nous allons maintenant préparer les données pour l’entrainement. Créez donc une nouvelle cellule Markdown dans votre notebook, et ajouter le titre suivant, et exécutez la cellule :
# 2. Préparer les données pour l'entrainement
Créez une nouvelle cellule Code dans votre notebook, ajouter le code suivant et exécutez la cellule :
# Préparer les données pour l'entraînement
def create_dataset(series, time_steps):
X, y = [], []
for i in range(len(series) - time_steps):
X.append(series[i:i + time_steps])
y.append(series[i + time_steps])
return np.array(X), np.array(y)
# Générer des séquences
time_steps = 10
values=df.values
X, y = create_dataset(values, time_steps)Affichez quelques données, et la forme de vos données :
print("Entrée : \n", X[0], " \nSortie : \n", y[0],"\n")
print("Entrée : \n", X[1], " \nSortie : \n", y[1],"\n")
print("Entrée : \n", X[2], " \nSortie : \n", y[2],"\n")
print(X.shape)
print(y.shape)Analysez la sortie de ces données \(0\), \(1\), et \(2\). Voyez vous une dépendance dans ces données ?
- Quel sera la première valeur l’entrée de la prochaine donnée X[3] ?
Nous allons maintenant séparer les données. Créez donc une nouvelle cellule Markdown dans votre notebook, et ajouter le titre suivant, et exécutez la cellule :
# 3. Séparer les données pour l'entrainement
et une cellule Code :
# Séparer en données d'entrainement et test
split = int(len(X) * 0.8)
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
print(X_train[0], y_train[0])
print(X_train[1], y_train[1])
print(X_train.shape)
print(y_train.shape)Nous allons maintenant définir le modèle. Créez donc une nouvelle cellule Markdown dans votre notebook, et ajouter le titre suivant, et exécutez la cellule :
# 4. Définir le modèle :
Ensuite, créez une cellule Code :
#Nombre de neurones cachés dans la couche RNN
m=10
# Define the SimpleRNN model
model = Sequential()
model.add(SimpleRNN(units=m, input_shape=(time_steps, 1)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
model.summary()Combien de paramètres sont dans la couche RNN pour \(m=2\) ?
Combien de paramètres sont dans la couche RNN pour \(m=3\) ? Vérifiez que cela correspond bien à l’illustration montré et expliqué en haut de ce TD.
Combien de paramètres sont dans la couche RNN pour \(m=4\) ?
Combien de paramètres sont dans la couche RNN pour \(m=10\) ?
Nous allons maintenant entraîner le modèle, créez une cellule Markdown :
# 5. Entrainer le modèle :
et une cellule de Code :
# Entrainer le modèle
history = model.fit(X_train, y_train, epochs=100, batch_size=16, validation_split=0.375)Exécutez.
Nous allons maintenant regarder les courbes d’apprentissage. Créez une cellule Markdown :
# 6. Courbes d'apprentissage
et une cellule de Code :
# Plot training and validation loss
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.legend()
plt.title('Loss Over Epochs')
plt.show()Exécutez.
Nous allons maintenant prédire des données de tests, créez une cellule Markdown :
# 7. Prédictions
et une cellule de Code :
# Evaluer et prédire données tests
y_pred = model.predict(X_test)
# Comparer prédictions avec valeurs réelles
plt.plot(y_test, label='Valeurs réelles')
plt.plot(y_pred, label='Valeurs prédites')
plt.legend()
plt.title(f"Prédiction avec time_steps {time_steps}, hidden_units {m}, noise_factor {noise_factor}")
plt.show()Nous remarquons que la qualité de prédiction est généralement assez bien. Mais qu’en est il de prédictions multiples, qui pour les prédictions successives, utilisent les prédictions générées ?
Pour cela, créez une cellule Markdown :
# 8. Prédictions multiples
et une cellule de Code :
# Prediction multiple pour le futur itérativement
num_future_steps = 100 # nombre d'itérations
last_sequence = values[-time_steps:] # Démarrer avec la dernière séquence
predicted_future = []
for i in range(num_future_steps):
if (i%20==0):
print(i/num_future_steps*100,"% calculé.")
#print(last_sequence)
next_pred = model.predict(last_sequence.reshape(1, time_steps, 1), verbose=0)[0, 0]
predicted_future.append(next_pred)
# Mettre à jour la séquence : enlever la première valeur, et ajouter la prédiction
last_sequence = np.append(last_sequence[1:], next_pred)Exécutez. Nous allons maintenant afficher le résultat de ces prédictions. Créez donc une cellule Code :
# Prédire le futur
import time # au cas où vous ne l'avez pas importé en haut
timestring = time.strftime("%Y%m%d-%H%M%S") # pour créer des noms de fichiers en fonction du temps
# Prédire le futur
plt.plot(values, label="Valeurs réelles")
future_index = range(len(values), len(values) + num_future_steps)
plt.plot(future_index, predicted_future, color='red', label="futur prédit")
plt.axvline(len(values) - 1, color='gray', linestyle='--', label="début de la prédiction")
plt.legend()
plt.title(f"Prédiction multiple, avec time_steps {time_steps}, hidden_units {m}, noise_factor {noise_factor}")
plt.xlabel("Temps t")
plt.ylabel("Valeur")
plt.savefig(f"image-multiples-{timestring}.png")
plt.show()Dans cette visualisation de données, dans l’axe horizontal, pour les temps t=0 jusqu’à t=99, c’est les valeurs d’entrées. A partir de t=100, on peut voir les valeurs prédites.
A partir de quelle valeur pour t, les données d’entrées ne contiennent plus de valeurs d’entrées connues ? En d’autres termes, à partir de quelle valeur pour t, l’intégralité des données d’entrées sont en fait des données prédites ?
Pourquoi le RNN arrive quand même à prédire les valeurs suivantes, même au-delà de la tendance des données d’entrées ?
Exercice 5.2 : Expérimentez avec les paramètres
Dans cet exercice, nous allons expérimenter avec différentes architectures.
Changez les nombre de hidden_units en m=4 et m=20
Changez le nombre de time_steps, pour chaque \(m\), de 5 et 20.
Introduisez du bruit : noise_factor=0.2 et même plus.
Faites plein de tests. La dernière image de prédictions multiples aura les paramètres dans le titre, vous pouvez donc créer un sous-dossier resultats dans votre dossier td5, et y sauvegardez les images systématiquement.
Nous pouvons aussi augmenter encore la difficulté de prédiction, en faisant augmenter l’amplitude de la courbe sinusoidale au fur et à mesure. Pour cela, dans votre cellule code juste après les importations de bibliothèques, changez la ligne de la “création de données” de votre première cellule Code après les importataions en :
x=np.sin(0.2*t) * 1.1*t/N + noise_factor*np.random.rand(N)Exécutez toutes les cellules.
- Est-ce que vous arrivez à faire des prédictions multiples correctement ? Gardez un screenshot de votre graphe de prédiction.
Exercice 5.3 : Données réelles
Créez un nouveau fichier td5-rnn-devises.ipynb. Créez des cellules pour importer les bibliothèques keras, tensorflow, pandas, matplotlib, comme d’habitude. Dans votre dossier td5, téléchargez les données CSV de cours d’échanges, c’est en bas de cette page, “Time Series”, “CSV (ZIP)”.
Charger le fichier CSV avec Pandas. Pour les valeurs, sélectionnez une dévise selon votre choix. Par exemple, pour USD, vous pouvez faire ça avec values = df.USD.values. Mais je préfère de loin que vous choisissez une autre devise selon vos goûts.
Attention, il faut inverser les valeurs, car la première valeur est la données la plus récente. En numpy, vous pouvez faire ça avec values = values[::-1].
N’oubliez pas d’augmenter le nombre de données \(N\) pour l’ajuster à votre nouveau jeu de données.
Complétez votre notebook pour faire des prédictions multiples de USD, tout comme dans le dernier exercice, comparé aux données réelles.
Plus précisément, entrainez votre modèle avec les valeurs jusqu’à une certaine date (dans le passée), et faites des prédictions pour un nombre important de jours suivant cette date.
Expérimentez avec différentes valeurs pour timestep et le nombre de neurones dans l’état caché (\(m\)).
Créer le graphe des prédictions multiples comme dans le dernier exercice. Comme titre, mettez votre “prénom-nom” ainsi que le nombre de timesteps et hidden units (m) que vous avez choisi.
- Prenez une copie d’écran de votre graphe. Est-ce que vous avez réussi à prédire quelque chose, au moins partiellement ?
1 Pour aller plus loin
Exercice optionnel 5.4 :
Le modèle d’un simple RNN a ses limites, notamment de “oublier” ce qui s’est passé il y a plus longtemps. Pour cela, il y a un autre modèle, appelé LSTM (long short term memory) qui a été introduit. Créez un nouveau notebook td5-lstm.ipynb, et remplacez la couche SimpleRNN par une couche LSTM. Expérimentez avec la prédiction de sinus et devises.
TD à rendre
A rendre sur moodle avant le 1 Décembre 2025 à 23h59. Pour cela, il y a des liens sur la page du cours (https://moodle.u-bordeaux.fr/course/view.php?id=22721).
Réponses aux questions
Soumettre une image d’une prédiction multiples sur des données et paramètres qui vous ont particulièrement percuté, accompagné de deux trois phrases expliquant pourquoi et ce que vous pensez avoir appris.