"""
Module: RNN et LSTM
Categorie: Deep Learning
Difficulte: Avance

Genere depuis la plateforme ML Formation
"""

# Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, mean_squared_error, r2_score

# Charger le dataset
df = pd.read_csv('stock_prices.csv')

# Explorer les donnees
# Type: Code executable
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)


# Preparer les sequences
# Type: Code executable
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)


# Simuler le comportement RNN
# Type: Code executable
print("=" * 70)
print("       SIMULATION D'UN RNN SIMPLE AVEC NUMPY")
print("=" * 70)

print("""
Pour vraiment comprendre les RNN, implementons une cellule RNN
simplifiee avec numpy. Cela montre le mecanisme de MEMOIRE.

RAPPEL DE LA FORMULE RNN:
  h_t = tanh(Wx * x_t + Wh * h_{t-1} + b)

Ou:
  • x_t = entree au temps t
  • h_{t-1} = etat cache precedent (memoire)
  • h_t = nouvel etat cache
  • Wx, Wh, b = parametres appris
""")

print("\n" + "=" * 70)
print("1. DEFINITION DE LA CELLULE RNN")
print("=" * 70)

def simple_rnn_cell(x, h_prev, Wx, Wh, b):
    """Une cellule RNN simple."""
    z = Wx * x + Wh * h_prev + b
    h = np.tanh(z)  # Activation tanh pour borner entre -1 et 1
    return h

print("""
FONCTION simple_rnn_cell():
  Input:
    • x: valeur d'entree au temps t
    • h_prev: etat cache du temps t-1
    • Wx, Wh, b: parametres

  Output:
    • h: nouvel etat cache (combine x et h_prev)
""")

print("\n" + "=" * 70)
print("2. PARAMETRES DU RNN")
print("=" * 70)

# Parametres (normalement appris par backpropagation)
np.random.seed(42)
Wx = 0.5   # Poids pour l'entree
Wh = 0.8   # Poids pour l'etat precedent (memoire)
b = 0.1    # Biais

print(f"""
PARAMETRES (fixes pour cette demo):
  • Wx = {Wx} (poids de l'entree)
  • Wh = {Wh} (poids de la memoire)
  • b = {b} (biais)

NOTE: Dans un vrai RNN, ces parametres sont APPRIS
par descente de gradient pour minimiser l'erreur de prediction.
""")

print("\n" + "=" * 70)
print("3. TRAITEMENT D'UNE SEQUENCE")
print("=" * 70)

# Sequence d'entree (10 premiers prix normalises)
sequence = df['price'].values[:10]
sequence_normalized = (sequence - sequence.mean()) / sequence.std()

print(f"""
SEQUENCE D'ENTREE (10 premiers jours):
  • Prix bruts: {sequence[:5].round(2)}...
  • Prix normalises: {sequence_normalized[:5].round(3)}...

POURQUOI NORMALISER?
  • tanh() fonctionne mieux avec des entrees proches de 0
  • Evite la saturation de l'activation
""")

# Traiter la sequence
h = 0  # Etat initial (pas de memoire au debut)
hidden_states = [h]

print("\n" + "-" * 60)
print(f"{'t':<5} {'x_t':<12} {'h_{t-1}':<12} {'z':<12} {'h_t'}")
print("-" * 60)

for t, x in enumerate(sequence_normalized):
    z = Wx * x + Wh * h + b
    h_new = np.tanh(z)
    print(f"{t:<5} {x:<12.3f} {h:<12.3f} {z:<12.3f} {h_new:.3f}")
    h = h_new
    hidden_states.append(h)

print("-" * 60)

print(f"""

OBSERVATIONS:
  • L'etat h evolue au fil du temps
  • Il "accumule" de l'information sur la sequence
  • h final = {hidden_states[-1]:.3f} resume toute la sequence!
""")

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

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

# Sequence d'entree
axes[0].plot(sequence_normalized, marker='o', color='#9B7AC4', linewidth=2, markersize=8)
axes[0].set_xlabel('Temps t', fontsize=11)
axes[0].set_ylabel('Entree x_t (normalisee)', fontsize=11)
axes[0].set_title('Sequence d\'Entree', fontsize=13, fontweight='bold')
axes[0].grid(True, alpha=0.3)

# Etats caches
axes[1].plot(hidden_states, marker='s', color='#F7E64D', linewidth=2, markersize=8)
axes[1].set_xlabel('Temps t', fontsize=11)
axes[1].set_ylabel('Etat cache h_t', fontsize=11)
axes[1].set_title('Evolution de la Memoire (Etat Cache)', fontsize=13, fontweight='bold')
axes[1].grid(True, alpha=0.3)

# Annotation
axes[1].annotate('h initial = 0\n(pas de memoire)', xy=(0, 0), xytext=(1, -0.3),
                 arrowprops=dict(arrowstyle='->', color='gray'), fontsize=10)
axes[1].annotate(f'h final = {hidden_states[-1]:.2f}\n(resume la sequence)',
                 xy=(len(hidden_states)-1, hidden_states[-1]),
                 xytext=(len(hidden_states)-3, hidden_states[-1]+0.2),
                 arrowprops=dict(arrowstyle='->', color='gray'), fontsize=10)

plt.tight_layout()
plt.show()

print("""
INTERPRETATION:
  • HAUT: la sequence d'entree (prix normalises)
  • BAS: l'etat cache qui evolue

L'etat cache "encode" l'historique de la sequence.
C'est cette MEMOIRE qui permet au RNN de capturer
les dependances temporelles!
""")

print("\n" + "=" * 70)
print("              SIMULATION RNN TERMINEE!")
print("=" * 70)


# Prediction avec modele simple
# Type: Code executable
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error

print("=" * 70)
print("       PREDICTION DE SERIES TEMPORELLES")
print("=" * 70)

print("""
Appliquons les concepts RNN a la prediction de prix!

APPROCHE SIMPLIFIEE:
Bien qu'on n'utilise pas un vrai RNN (qui necessite PyTorch/TensorFlow),
on va simuler le principe avec des sequences + regression.

OBJECTIF:
  Predire le prix de demain a partir des 5 derniers jours.
""")

print("\n" + "=" * 70)
print("1. PREPARATION DES DONNEES")
print("=" * 70)

prices = df['price'].values
seq_length = 5

def create_sequences(data, seq_length):
    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)

X, y = create_sequences(prices, seq_length)

print(f"""
SEQUENCES CREEES:
  • Longueur de sequence: {seq_length} jours
  • Nombre de sequences: {len(X)}
  • Shape X: {X.shape}
  • Shape y: {y.shape}
""")

print("\n" + "=" * 70)
print("2. DIVISION TRAIN/TEST (SEQUENTIELLE!)")
print("=" * 70)

# Division sequentielle (pas aleatoire!)
split = int(0.8 * len(X))
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]

print(f"""
DIVISION:
  • Entrainement: {len(X_train)} sequences (premiers 80%)
  • Test: {len(X_test)} sequences (derniers 20%)

IMPORTANT: Division SEQUENTIELLE!
  Pour les series temporelles, on ne peut PAS melanger aleatoirement.
  Le modele doit apprendre sur le passe pour predire le futur.
""")

print("\n" + "=" * 70)
print("3. NORMALISATION")
print("=" * 70)

scaler_X = StandardScaler()
scaler_y = StandardScaler()

X_train_scaled = scaler_X.fit_transform(X_train)
X_test_scaled = scaler_X.transform(X_test)
y_train_scaled = scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel()

print(f"""
NORMALISATION:
  • Moyenne X train: {X_train.mean():.2f} → {X_train_scaled.mean():.4f}
  • Std X train: {X_train.std():.2f} → {X_train_scaled.std():.4f}

La normalisation ameliore la convergence et la stabilite.
""")

print("\n" + "=" * 70)
print("4. ENTRAINEMENT DU MODELE")
print("=" * 70)

model = LinearRegression()
model.fit(X_train_scaled, y_train_scaled)

print(f"""
MODELE: Regression Lineaire sur Sequences

INTERPRETATION:
  Le modele apprend des poids pour chaque jour de la sequence:
""")

print("-" * 45)
print(f"{'Jour':<15} {'Coefficient':<15} {'Interpretation'}")
print("-" * 45)
for i, coef in enumerate(model.coef_):
    impact = "+" if coef > 0 else "-"
    importance = "Fort" if abs(coef) > 0.3 else "Moyen" if abs(coef) > 0.1 else "Faible"
    print(f"J-{seq_length-i:<12} {coef:+.4f}         {impact} Impact {importance}")
print("-" * 45)

print("""
Les jours recents ont generalement plus d'influence!
""")

print("\n" + "=" * 70)
print("5. PREDICTIONS ET EVALUATION")
print("=" * 70)

# Predictions
y_pred_scaled = model.predict(X_test_scaled)
y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()

# Metriques
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100

print(f"""
METRIQUES D'EVALUATION:
═══════════════════════════════════════════
  • MSE (Mean Squared Error): {mse:.4f}
  • RMSE (Root MSE): {rmse:.4f}
  • MAE (Mean Absolute Error): {mae:.4f}
  • MAPE (Mean Absolute % Error): {mape:.2f}%
═══════════════════════════════════════════

INTERPRETATION:
  • RMSE de {rmse:.2f} signifie que l'erreur typique est de {rmse:.2f} unites
  • MAPE de {mape:.1f}% = erreur moyenne en pourcentage du prix reel
""")

if mape < 5:
    print("  → Excellent! Erreur < 5%")
elif mape < 10:
    print("  → Bon! Erreur entre 5% et 10%")
else:
    print("  → Peut etre ameliore. Erreur > 10%")

print("\n" + "=" * 70)
print("6. VISUALISATION DES PREDICTIONS")
print("=" * 70)

plt.figure(figsize=(14, 6))
plt.plot(range(len(y_test)), y_test, label='Prix Reel', color='#9B7AC4', linewidth=2)
plt.plot(range(len(y_pred)), y_pred, label='Prediction', color='#F7E64D', linewidth=2, linestyle='--')
plt.fill_between(range(len(y_test)), y_test, y_pred, alpha=0.2, color='#e74c3c')
plt.xlabel('Jours (ensemble de test)', fontsize=11)
plt.ylabel('Prix', fontsize=11)
plt.title(f'Prediction vs Realite (RMSE: {rmse:.2f}, MAPE: {mape:.1f}%)', fontsize=13, fontweight='bold')
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

print("""
OBSERVATIONS:
  • La ligne jaune (prediction) suit la ligne violette (reel)
  • Les ecarts sont les erreurs de prediction
  • Un vrai LSTM pourrait capturer des patterns plus complexes!
""")

print("\n" + "=" * 70)
print("              PREDICTION TERMINEE!")
print("=" * 70)


# Exercice: Predire plusieurs jours
# Type: Exercice
# Exercice: Prediction multi-step

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error

print("=" * 70)
print("       EXERCICE: PREDICTION MULTI-STEP")
print("=" * 70)

print("""
OBJECTIF:
Modifier le code pour predire les 3 PROCHAINS jours
au lieu d'un seul!

DEFI:
  • Entree: 5 derniers jours
  • Sortie: 3 prochains jours (pas juste 1!)

QUESTIONS:
  • Comment modifier create_sequences()?
  • Quel type de modele utiliser pour multi-output?
""")

# Preparez les donnees
prices = df['price'].values
seq_length = 5

# TODO: Modifiez create_sequences pour predire les 3 prochains jours
# au lieu d'un seul

# TODO: Entrainez un modele multi-output
# TODO: Visualisez les predictions

print("""
VOTRE CODE ICI:
---------------
# Indice: utilisez MultiOutputRegressor de sklearn
""")


# Applications et frameworks
# Type: Code executable
print("=" * 70)
print("       APPLICATIONS ET FRAMEWORKS RNN/LSTM")
print("=" * 70)

print("""
Les RNN et LSTM ont revolutionne le traitement des sequences.
Voici un panorama des applications et outils.
""")

print("\n" + "=" * 70)
print("1. APPLICATIONS DES RNN/LSTM")
print("=" * 70)

applications = [
    ("Prediction de series temporelles", "Bourse, meteo, energie, ventes", "★★★"),
    ("Traitement du langage naturel", "Traduction, chatbots, resume", "★★★"),
    ("Generation de texte", "GPT (Transformer, evolution)", "★★★"),
    ("Reconnaissance vocale", "Siri, Alexa, Google Assistant", "★★★"),
    ("Analyse de sentiments", "Avis clients, tweets, reviews", "★★☆"),
    ("Generation de musique", "Composition automatique, MIDI", "★★☆"),
    ("Video", "Legende automatique, prediction", "★★☆"),
]

print("-" * 70)
print(f"{'Application':<35} {'Exemples':<30} {'Importance'}")
print("-" * 70)
for app, examples, stars in applications:
    print(f"{app:<35} {examples:<30} {stars}")
print("-" * 70)

print("\n" + "=" * 70)
print("2. EVOLUTION: RNN → LSTM → TRANSFORMER")
print("=" * 70)

print("""
┌──────────────────────────────────────────────────────────────────────┐
│  1980s: RNN Simple                                                   │
│    • Concept de memoire                                              │
│    • Probleme: gradient disparait                                    │
├──────────────────────────────────────────────────────────────────────┤
│  1997: LSTM (Hochreiter & Schmidhuber)                              │
│    • Mecanisme de portes                                             │
│    • Memoire long terme efficace                                     │
├──────────────────────────────────────────────────────────────────────┤
│  2014: GRU (Cho et al.)                                             │
│    • Version simplifiee du LSTM                                      │
│    • 2 portes au lieu de 3                                           │
├──────────────────────────────────────────────────────────────────────┤
│  2017: Transformer (Vaswani et al.)                                 │
│    • Mecanisme d'attention                                           │
│    • Plus parallelisable                                             │
│    • Base de GPT, BERT, ChatGPT                                      │
└──────────────────────────────────────────────────────────────────────┘
""")

print("\n" + "=" * 70)
print("3. FRAMEWORKS POUR IMPLEMENTER RNN/LSTM")
print("=" * 70)

frameworks = [
    ("PyTorch", "torch.nn.LSTM, torch.nn.GRU", "Recherche, flexibilite"),
    ("TensorFlow/Keras", "keras.layers.LSTM", "Production, simplicite"),
    ("Hugging Face", "Transformers library", "NLP, modeles pre-entraines"),
    ("FastAI", "AWD-LSTM", "Apprentissage rapide"),
]

print("-" * 70)
print(f"{'Framework':<18} {'Classes/Modules':<25} {'Points forts'}")
print("-" * 70)
for name, classes, strengths in frameworks:
    print(f"{name:<18} {classes:<25} {strengths}")
print("-" * 70)

print("\n" + "=" * 70)
print("4. RNN/LSTM vs TRANSFORMER: QUAND UTILISER QUOI?")
print("=" * 70)

print("""
┌───────────────────────┬───────────────────────┬───────────────────────┐
│ Critere               │ RNN/LSTM              │ Transformer           │
├───────────────────────┼───────────────────────┼───────────────────────┤
│ Series temporelles    │ ★★★ Tres bon          │ ★★☆ Bon               │
│ Texte court           │ ★★☆ Bon               │ ★★★ Excellent         │
│ Texte long            │ ★☆☆ Difficile         │ ★★★ Excellent         │
│ Temps d'entrainement  │ ★☆☆ Lent (sequentiel) │ ★★★ Rapide (parallele)│
│ Memoire GPU           │ ★★★ Faible            │ ★☆☆ Eleve             │
│ Interpretabilite      │ ★★☆ Moyenne           │ ★★★ Attention visible │
└───────────────────────┴───────────────────────┴───────────────────────┘
""")

print("\n" + "=" * 70)
print("5. RESSOURCES POUR APPROFONDIR")
print("=" * 70)

print("""
COURS GRATUITS:
  • CS224n Stanford - NLP avec Deep Learning
  • fast.ai - Practical Deep Learning
  • Coursera - Sequence Models (Andrew Ng)

ARTICLES FONDATEURS:
  • LSTM: "Long Short-Term Memory" (1997)
  • GRU: "Learning Phrase Representations" (2014)
  • Transformer: "Attention Is All You Need" (2017)

TUTORIELS PRATIQUES:
  • PyTorch LSTM Tutorial
  • Keras Time Series Forecasting
  • Hugging Face Course (NLP)
""")

print("\n" + "=" * 70)
print("              MODULE RNN/LSTM TERMINE!")
print("=" * 70)
print("""
CE QUE VOUS AVEZ APPRIS:
  ✓ Pourquoi les donnees sequentielles necessitent des RNN
  ✓ Le mecanisme de memoire (etat cache)
  ✓ Le probleme du gradient qui disparait
  ✓ Comment LSTM resout ce probleme avec des portes
  ✓ Application a la prediction de series temporelles

PROCHAINES ETAPES:
  → Implementer un vrai LSTM avec PyTorch
  → Explorer les Transformers et l'attention
  → Appliquer a vos propres series temporelles
""")

