Formation ML / Deep learning

RNN et LSTM

Avancé 55 min 13 sections

L'état caché porte la mémoire d'un pas au suivant ; trois portes la sauvent du gradient évanescent.

Objectifs d'apprentissage

  • Comprendre le concept de mémoire dans les réseaux de neurones
  • Connaître le problème du gradient qui disparaît
  • Comprendre l'architecture LSTM et ses portes
  • Appliquer les concepts aux séries temporelles

Prérequis

Module Réseau de neurones recommandé

Le code de cette page s'exécute dans votre navigateur. Le premier lancement télécharge Python et ses bibliothèques.

Théorie

Pourquoi des réseaux récurrents?

Les données séquentielles ont une structure temporelle que les réseaux classiques ignorent.

Exemples de séquences:

  • Séries temporelles (bourse, météo, capteurs)
  • Texte (mots dans une phrase)
  • Audio (parole, musique)
  • Vidéo (suite d'images)

Problème des réseaux classiques:

  • Traitent chaque entrée indépendamment
  • Pas de notion de "contexte" ou "historique"
  • Le mot "banque" à un sens différent selon le contexte!

Solution: Les RNN Un Récurrent Neural Network maintient un état cache (hidden state) qui encode l'historique de la séquence.

Intuition:

"Pour comprendre ce mot, je dois me souvenir des mots précédents."

Applications:

  • Prédiction de séries temporelles
  • Traduction automatique
  • Génération de texte
  • Reconnaissance vocale
Théorie

Schéma: Architecture RNN

Le RNN "déroule" dans le temps:

flowchart LR subgraph T1 ["t=1"] X1["x1"] H1["h1"] Y1["y1"] end subgraph T2 ["t=2"] X2["x2"] H2["h2"] Y2["y2"] end subgraph T3 ["t=3"] X3["x3"] H3["h3"] Y3["y3"] end X1 --> H1 --> Y1 X2 --> H2 --> Y2 X3 --> H3 --> Y3 H1 -->|"mémoire"| H2 -->|"mémoire"| H3 class H1 ml-node-main class H2 ml-node-main class H3 ml-node-main

Formule du RNN:

$$h_t = \tanh(W_x x_t + W_h h_{t-1} + b)$$

$$y_t = W_y h_t + b_y$$

Problème: Gradient qui disparaît

flowchart LR G1["Gradient fort
(recent)"] G2["Gradient moyen"] G3["Gradient faible"] G4["Gradient ~0
(ancien)"] G1 --> G2 --> G3 --> G4 class G1 ml-node-accent class G4 ml-node-muted

Le gradient devient très petit pour les entrées anciennes → le RNN "oublie" le passe lointain.

Exemple concret avec code couleur - Calcul d'un état cache RNN:

Entrée au temps t: $\textcolor{#3498db}{x_t = 0.8}$ (prix normalise) État précédent: $\textcolor{#e67e22}{h_{t-1} = 0.3}$ (mémoire) Poids: $\textcolor{#9B7AC4}{W_x = 0.5}$, $\textcolor{#F7E64D}{W_h = 0.7}$, $\textcolor{#9B7AC4}{b = 0.1}$

Calcul du nouvel état cache:

$h_t = \tanh(\textcolor{#9B7AC4}{W_x} \cdot \textcolor{#3498db}{x_t} + \textcolor{#F7E64D}{W_h} \cdot \textcolor{#e67e22}{h_{t-1}} + \textcolor{#9B7AC4}{b})$

$h_t = \tanh(\textcolor{#9B7AC4}{0.5} \times \textcolor{#3498db}{0.8} + \textcolor{#F7E64D}{0.7} \times \textcolor{#e67e22}{0.3} + \textcolor{#9B7AC4}{0.1})$

$h_t = \tanh(\textcolor{#3498db}{0.40} + \textcolor{#e67e22}{0.21} + \textcolor{#9B7AC4}{0.10}) = \tanh(0.71) = \textcolor{#27ae60}{\mathbf{0.61}}$

Interprétation:

  • Le nouvel état $\textcolor{#27ae60}{h_t = 0.61}$ combine:
  • L'entrée actuelle ($\textcolor{#3498db}{0.40}$)
  • La mémoire du passe ($\textcolor{#e67e22}{0.21}$)
  • Cette "mémoire" sera transmise à t+1

Légende des couleurs:

  • $\textcolor{#3498db}{Bleu}$: Entrée actuelle $x_t$ et sa contribution
  • $\textcolor{#e67e22}{Orange}$: État précédent $h_{t-1}$ (mémoire)
  • $\textcolor{#9B7AC4}{Violet}$: Poids d'entrée $W_x$ et biais
  • $\textcolor{#F7E64D}{Jaune}$: Poids récurrent $W_h$
  • $\textcolor{#27ae60}{Vert}$: Nouvel état cache $h_t$
Avancé Exercice manuel: À vous de calculer!

Objectif: Comprendre le fonctionnement des RNN à la main (état cache, séquence).

CONTEXTE

RNN simple avec :

  • Entrée : vecteur de dimension 1
  • État cache : dimension 1
  • Poids : $W_h = 0.5$ (état vers état), $W_x = 0.8$ (entrée vers état)
  • Biais : $b = 0$
  • Activation : tanh (valeurs entre -1 et 1)

Séquence d'entrée : $x = [1, 0.5, -0.5]$

État initial : $h_0 = 0$

Formule : $h_t = \tanh(W_h \cdot h_{t-1} + W_x \cdot x_t + b)$

Valeurs tanh : tanh(0)=0, tanh(0.5)≈0.46, tanh(0.8)≈0.66, tanh(1)≈0.76

PARTIE 1 : Propagation pas à pas

1.1) Calculez $h_1$ pour $x_1 = 1$ 1.2) Calculez $h_2$ pour $x_2 = 0.5$ 1.3) Calculez $h_3$ pour $x_3 = -0.5$

PARTIE 2 : Interprétation

2.1) Comment l'état cache "mémorise" les entrées précédentes ? 2.2) Que représente $h_3$ par rapport à toute la séquence ?

Avancé Solution de l'exercice manuel

SOLUTION DÉTAILLÉE

$W_h = 0.5$, $W_x = 0.8$, $h_0 = 0$

PARTIE 1 : Propagation pas à pas

1.1) Pas 1 ($x_1 = 1$) :

$$h_1 = \tanh(0.5 \cdot 0 + 0.8 \cdot 1) = \tanh(0.8)$$

$\boxed{h_1 = \textcolor{#3498db}{0.66}}$

1.2) Pas 2 ($x_2 = 0.5$) :

$$h_2 = \tanh(0.5 \cdot 0.66 + 0.8 \cdot 0.5)$$

$$= \tanh(0.33 + 0.4) = \tanh(0.73)$$

$\boxed{h_2 = \textcolor{#e67e22}{0.62}}$

1.3) Pas 3 ($x_3 = -0.5$) :

$$h_3 = \tanh(0.5 \cdot 0.62 + 0.8 \cdot (-0.5))$$

$$= \tanh(0.31 - 0.4) = \tanh(-0.09)$$

$\boxed{h_3 = \textcolor{#27ae60}{-0.09}}$

PasEntrée $x_t$État $h_{t-1}$Calcul$h_t$
11.00tanh(0.8)0.66
20.50.66tanh(0.73)0.62
3-0.50.62tanh(-0.09)-0.09

PARTIE 2 : Interprétation

2.1) Mémoire via l'état cache :

  • $h_1$ dépend de $x_1$
  • $h_2$ dépend de $x_2$ ET de $h_1$ (donc indirectement de $x_1$)
  • $h_3$ "résumé" toute la séquence $[x_1, x_2, x_3]$

2.2) $h_3$ est un encodage de la séquence entière. C'est pourquoi les RNN sont utilisés pour le texte, les séries temporelles, etc.

$\boxed{\text{L'état final contient l'information de toute la séquence}}$

Légende : $\textcolor{#3498db}{Bleu}$: h1, $\textcolor{#e67e22}{Orange}$: h2, $\textcolor{#27ae60}{Vert}$: h3

Code

Explorer les données

print("=" * 70)
print("       EXPLORATION DE LA SERIE TEMPORELLE")
print("=" * 70)

print("""
Les RNN/LSTM sont specialises pour les DONNEES SEQUENTIELLES.
Nous allons travailler avec une serie temporelle de prix boursiers.

OBJECTIF:
  Apprendre a predire le prix de demain a partir de l'historique recent.

POURQUOI LES RNN?
  • Les prix d'aujourd'hui dependent des prix passes
  • Il y a une MEMOIRE dans les donnees
  • Un reseau classique ignorerait cette dependance temporelle
""")

print("\n" + "=" * 70)
print("1. APERCU DES DONNEES")
print("=" * 70)
display(df.head(15), title="Serie Temporelle (Prix Boursiers)")

print("""
STRUCTURE:
  • 'day': jour (index temporel)
  • 'price': prix de l'action a ce jour
""")

print("\n" + "=" * 70)
print("2. STATISTIQUES DE LA SERIE")
print("=" * 70)

prices = df['price'].values
n_days = len(prices)

print(f"""
PERIODE:
  • Premier jour: {df['day'].min()}
  • Dernier jour: {df['day'].max()}
  • Nombre total de jours: {n_days}

STATISTIQUES DES PRIX:
  • Prix minimum: {prices.min():.2f}
  • Prix maximum: {prices.max():.2f}
  • Prix moyen: {prices.mean():.2f}
  • Ecart-type: {prices.std():.2f}
  • Amplitude: {prices.max() - prices.min():.2f}
""")

print("\n" + "=" * 70)
print("3. ANALYSE DE LA VOLATILITE")
print("=" * 70)

# Calculer les rendements journaliers
returns = np.diff(prices) / prices[:-1] * 100

print(f"""
RENDEMENTS JOURNALIERS:
  • Moyenne: {returns.mean():+.2f}%
  • Ecart-type (volatilite): {returns.std():.2f}%
  • Min: {returns.min():+.2f}% (pire journee)
  • Max: {returns.max():+.2f}% (meilleure journee)

INTERPRETATION:
  Une volatilite de {returns.std():.2f}% signifie que le prix varie
  typiquement de +/-{returns.std():.2f}% d'un jour a l'autre.
""")

print("\n" + "=" * 70)
print("4. VISUALISATION")
print("=" * 70)

fig, axes = plt.subplots(2, 1, figsize=(14, 8))

# Serie temporelle
axes[0].plot(df['day'], df['price'], color='#9B7AC4', linewidth=2)
axes[0].fill_between(df['day'], df['price'], alpha=0.3, color='#9B7AC4')
axes[0].set_xlabel('Jour', fontsize=11)
axes[0].set_ylabel('Prix', fontsize=11)
axes[0].set_title('Evolution du Prix Boursier', fontsize=13, fontweight='bold')
axes[0].grid(True, alpha=0.3)

# Marquer min et max
min_idx = prices.argmin()
max_idx = prices.argmax()
axes[0].scatter([df['day'].iloc[min_idx]], [prices.min()], s=100, c='#e74c3c', zorder=5, label=f'Min: {prices.min():.2f}')
axes[0].scatter([df['day'].iloc[max_idx]], [prices.max()], s=100, c='#27ae60', zorder=5, label=f'Max: {prices.max():.2f}')
axes[0].legend()

# Distribution des rendements
axes[1].hist(returns, bins=20, color='#F7E64D', edgecolor='black', alpha=0.7)
axes[1].axvline(x=0, color='#e74c3c', linestyle='--', linewidth=2, label='0%')
axes[1].axvline(x=returns.mean(), color='#9B7AC4', linestyle='-', linewidth=2, label=f'Moyenne: {returns.mean():.2f}%')
axes[1].set_xlabel('Rendement journalier (%)', fontsize=11)
axes[1].set_ylabel('Frequence', fontsize=11)
axes[1].set_title('Distribution des Rendements Journaliers', fontsize=13, fontweight='bold')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("""
OBSERVATIONS:
  • La serie presente des tendances et des fluctuations
  • Les rendements sont approximativement centres autour de 0
  • Ces patterns temporels sont ce que le RNN va apprendre!
""")

print("\n" + "=" * 70)
print("              EXPLORATION TERMINEE!")
print("=" * 70)
Ctrl+Entrée

Cliquez sur « Exécuter » pour voir le résultat.

Code

Préparer les séquences

print("=" * 70)
print("       PREPARATION DES SEQUENCES POUR LE RNN")
print("=" * 70)

print("""
Pour entrainer un RNN, on transforme la serie temporelle en SEQUENCES.
Chaque sequence est une "fenetre glissante" sur les donnees.

PRINCIPE:
  Input: [prix_t-4, prix_t-3, prix_t-2, prix_t-1, prix_t]
  Output: prix_t+1 (le jour suivant)

Le RNN apprend: "Etant donne les 5 derniers jours, predire demain"
""")

print("\n" + "=" * 70)
print("1. FONCTION DE CREATION DE SEQUENCES")
print("=" * 70)

def create_sequences(data, seq_length):
    """Cree des paires (X, y) pour l'entrainement du RNN."""
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i:i+seq_length])
        y.append(data[i+seq_length])
    return np.array(X), np.array(y)

print("""
FONCTION create_sequences():
  • data: la serie temporelle complete
  • seq_length: nombre de pas de temps en entree
  • Retourne: X (sequences), y (cibles)

COMMENT CA MARCHE:
  Pour chaque position i dans les donnees:
    - X[i] = [data[i], data[i+1], ..., data[i+seq_length-1]]
    - y[i] = data[i+seq_length]
""")

print("\n" + "=" * 70)
print("2. APPLICATION AUX DONNEES")
print("=" * 70)

prices = df['price'].values
seq_length = 5  # Utiliser 5 jours pour predire le suivant

X, y = create_sequences(prices, seq_length)

print(f"""
PARAMETRES:
  • Longueur de sequence: {seq_length} jours
  • Donnees totales: {len(prices)} jours

RESULTAT:
  • Nombre de sequences: {len(X)}
  • Shape de X: {X.shape} → ({len(X)} sequences, {seq_length} jours chacune)
  • Shape de y: {y.shape} → ({len(y)} valeurs cibles)

POURQUOI {len(X)} sequences?
  {len(prices)} - {seq_length} = {len(X)}
  (on perd {seq_length} points pour creer la premiere sequence complete)
""")

print("\n" + "=" * 70)
print("3. EXEMPLES DE SEQUENCES")
print("=" * 70)

print("""
Visualisons quelques exemples pour comprendre la structure:
""")

print("-" * 65)
print(f"{'Sequence':<10} {'Entree (5 derniers jours)':<40} {'Cible'}")
print("-" * 65)

for i in range(5):
    input_str = "[" + ", ".join([f"{x:.1f}" for x in X[i]]) + "]"
    print(f"{i:<10} {input_str:<40} {y[i]:.2f}")

print("-" * 65)

print("""
LECTURE:
  • Sequence 0: jours 1-5 → predire jour 6
  • Sequence 1: jours 2-6 → predire jour 7
  • etc.

La fenetre "glisse" d'un jour a chaque sequence.
""")

print("\n" + "=" * 70)
print("4. VISUALISATION D'UNE SEQUENCE")
print("=" * 70)

fig, ax = plt.subplots(figsize=(12, 5))

# Choisir une sequence
idx = 10

# Tracer l'historique
x_hist = range(idx, idx + seq_length)
ax.plot(x_hist, X[idx], 'o-', color='#9B7AC4', linewidth=2, markersize=10,
        label=f'Entree ({seq_length} jours)')

# Tracer la cible
ax.plot(idx + seq_length, y[idx], 's', color='#F7E64D', markersize=15,
        label='Cible a predire')

# Tracer le contexte complet
ax.plot(range(len(prices)), prices, '--', color='gray', alpha=0.3, label='Serie complete')

ax.axvline(x=idx + seq_length - 0.5, color='#e74c3c', linestyle=':', linewidth=2, alpha=0.7)
ax.text(idx + seq_length - 0.3, prices.max(), 'Frontiere\nprediction', fontsize=10, color='#e74c3c')

ax.set_xlabel('Jour', fontsize=11)
ax.set_ylabel('Prix', fontsize=11)
ax.set_title(f'Exemple de Sequence #{idx}', fontsize=13, fontweight='bold')
ax.legend(loc='best')
ax.grid(True, alpha=0.3)
ax.set_xlim(idx - 2, idx + seq_length + 3)

plt.tight_layout()
plt.show()

print(f"""
CETTE SEQUENCE:
  • Entree: prix des jours {idx} a {idx + seq_length - 1}
  • Cible: prix du jour {idx + seq_length}

Le RNN va apprendre a predire le point jaune
a partir des points violets!
""")

print("\n" + "=" * 70)
print("              SEQUENCES PREPAREES!")
print("=" * 70)
Ctrl+Entrée

Cliquez sur « Exécuter » pour voir le résultat.

Contenu verrouillé
6 / 13

Continuez votre apprentissage

Vous avez exploré 6 sections de ce module. Connectez-vous pour débloquer le reste du cours, incluant les exercices pratiques et les solutions.

Ctrl ou Cmd plus Entrée exécute le code. Tab indente ; Échap puis Tab quitte l'éditeur.