{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 🚲 Découvrir un réseau de neurones en l'expérimentant : affluence d'une station de vélos\n",
    "\n",
    "Ce notebook accompagne la page interactive **« Apprendre à prévoir l'affluence d'une station de vélos »** (`reseau_velo_affluence.html`). Une station de vélos en libre-service est équipée de capteurs ; à partir de **5 informations** (heure, jour ouvré ou week-end, température, pluie, vent), le réseau de neurones apprend à prévoir si l'**affluence** sera **faible**, **moyenne** ou **forte**. La page affiche les valeurs en unités réelles (heures, °C, mm/h, km/h) ; en interne, elles sont ramenées entre 0 et 1.\n",
    "\n",
    "**Durée indicative : 2 h 30 à 3 h** (ou deux séances : parties A à D, puis partie E). 15 questions progressives, plus une partie optionnelle en Keras.\n",
    "\n",
    "## Règles du jeu\n",
    "- **Chaque question demande de manipuler la page** et de noter ce que tu observes. Une réponse sans observation ne compte pas.\n",
    "- Écris tes réponses dans les cellules « ✏️ Ta réponse » (double-clique pour modifier, `Maj + Entrée` pour valider).\n",
    "- Quand une question demande un calcul, utilise la cellule de code juste en dessous.\n",
    "- **Repère important** : quand tu recharges la page (F5), tout le monde obtient **exactement le même réseau au départ**. Tes nombres doivent donc être identiques à ceux de ton voisin, jusqu'à ce que tu cliques sur **🎲 Autre départ**.\n",
    "- Si tu cliques sur « Apprendre » alors que la question demande « 1 cycle », recharge la page (F5) et recommence.\n",
    "\n",
    "Exécute la première cellule de code : elle ouvre la page dans ton navigateur (le fichier HTML doit être dans le même dossier que ce notebook). Garde la page ouverte dans un onglet à côté du notebook.\n",
    "\n",
    "## 📖 Le vocabulaire que tu vas rencontrer\n",
    "\n",
    "Les mots de ce tableau reviendront dans les questions. Lis-le maintenant, et reviens-y quand un mot te gêne : à la fin, tu dois savoir les réexpliquer (question 15).\n",
    "\n",
    "| Mot | Ce que cela veut dire | Où le rencontrer |\n",
    "|---|---|---|\n",
    "| **Entrée** (*feature*) | Une information donnée au réseau : heure, jour, température, pluie, vent. Elle est **normalisée** (ramenée entre 0 et 1). | page, questions 1 et 14 |\n",
    "| **Neurone** | Une petite unité qui calcule une somme pondérée de ses entrées, ajoute un biais et applique une fonction d'activation. | question 5 |\n",
    "| **Poids** | Un coefficient de chaque connexion : il dit si le neurone « vote pour » ou « contre », et avec quelle force. C'est ce que le réseau **apprend**. | questions 5, 6 et 12 |\n",
    "| **Biais** | Un réglage propre à chaque neurone, ajouté à la somme. Il est lui aussi appris. | questions 5 et 12 |\n",
    "| **Activation** | La valeur de sortie d'un neurone, entre 0 et 1 pour la sigmoïde. | questions 5 et 11 |\n",
    "| **Sigmoïde / softmax** | Deux fonctions : la sigmoïde « écrase » une somme entre 0 et 1 ; le softmax transforme les 3 sorties en 3 scores dont la somme vaut 100 %. | questions 5 et 11 |\n",
    "| **Propagation avant** (*forward*) | Le calcul des entrées vers les scores, couche après couche. C'est une **prédiction**. | questions 5 et 11 |\n",
    "| **Erreur** (*loss*, fonction de perte) | Un nombre qui mesure l'écart entre les scores et la bonne réponse. Ici : −ln(score de la bonne classe). | questions 3 et 10 |\n",
    "| **Rétropropagation** (*backprop*) | Le calcul qui renvoie l'erreur **en arrière**, de la sortie vers les couches cachées, pour savoir quoi corriger. | question 12 |\n",
    "| **Gradient** | Pour un poids : de combien l'erreur change si on le change un peu (son signe donne le sens à suivre). | question 12 |\n",
    "| **Descente de gradient** | La méthode qui corrige chaque poids d'un petit pas dans le sens qui diminue l'erreur. | question 12 |\n",
    "| **Taux d'apprentissage** (*learning rate*) | La taille de ce pas (0,18 sur la page). Trop petit : très lent. Trop grand : le réseau diverge. | question 13 |\n",
    "| **Époque** (*epoch*) | Un passage complet sur tous les exemples d'apprentissage. **Un « cycle » de la page = 5 époques.** | questions 2 et 13 |\n",
    "| **Lot** (*batch*) et **itération** | Un lot est un groupe d'exemples traités avant de corriger les poids ; une itération est une correction. La page corrige après **chaque** exemple (lot de 1). | question 13 |\n",
    "| **Jeu d'apprentissage / de test** | Les exemples qui servent à apprendre / ceux, jamais vus, qui servent à vérifier. | questions 4 et 10 |\n",
    "| **Surapprentissage** (*overfitting*) | Le réseau colle aux exemples d'apprentissage et généralise moins bien. | questions 9, 10 et 13 |\n",
    "| **Paramètres / hyperparamètres** | Les paramètres (poids, biais) sont **appris** ; les hyperparamètres (taux, neurones cachés, époques, lot) sont **choisis**. | question 13 |"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import os\n",
    "import webbrowser\n",
    "\n",
    "PAGE = \"reseau_velo_affluence.html\"   # le fichier HTML doit être dans le même dossier que ce notebook\n",
    "webbrowser.open(os.path.abspath(PAGE))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# Partie A — Observer\n",
    "_Premiers contacts : lire les scores, suivre l'apprentissage, comprendre l'erreur._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 1 — Avant d'apprendre : que sait le réseau ?\n",
    "\n",
    "**🎯 Objectif :** Lire l'état initial d'un réseau non entraîné et comprendre pourquoi ses réponses sont fausses.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5). Dans le menu « Tester », laisse **🔥 Jour ouvré, 8 h (apprentissage)**.\n",
    "2. Lis les 5 informations de cette situation (ligne sous les curseurs « Modifier la situation »).\n",
    "3. Lis les 3 scores sous les curseurs, ainsi que « Réussite apprentissage », « Réussite test » et « Erreur moyenne apprentissage ».\n",
    "\n",
    "**❓ Questions :**\n",
    "- Recopie les 5 informations et les 3 scores. Quelle est la somme des trois scores ?\n",
    "- Le réseau reconnaît-il cette situation (forte affluence) ? Que répond-il, et à quel rang se trouve la bonne réponse ?\n",
    "- Que signifie une réussite d'apprentissage aussi basse ? Dans la cellule de code, calcule `np.log(3)` et compare avec l'erreur moyenne lue dans la page."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "np.log(3)   # compare avec l'erreur moyenne lue dans la page"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 2 — Apprendre cycle par cycle\n",
    "\n",
    "**🎯 Objectif :** Observer l'évolution des scores et des indicateurs pendant les premiers cycles.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Toujours sur **🔥 Jour ouvré, 8 h (apprentissage)**, clique sur **« 1 cycle »** (un cycle de la page = 5 passages sur les 9 exemples, soit 5 **époques**) et lis les valeurs après le cycle 1, puis le cycle 5, puis le cycle 10 (clique plusieurs fois, sans utiliser « Apprendre »).\n",
    "2. Complète le tableau (le cycle 0 est déjà connu par la question 1).\n",
    "\n",
    "**❓ Questions :**\n",
    "- Complète le tableau.\n",
    "- À partir de quel cycle la forte affluence devient-elle la réponse n° 1 du réseau pour cette situation ? Cherche-le avec « 1 cycle ».\n",
    "- Au cycle 10, la réussite sur l'apprentissage est de 78 % : que signifie ce nombre en nombre d'exemples ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**📋 À compléter :**\n",
    "\n",
    "| Cycle | 💤 Faible | 🚲 Moyenne | 🔥 Forte | Erreur moyenne | Réussite apprentissage | Réussite test |\n",
    "|---|---|---|---|---|---|---|\n",
    "| 0 | | | | | | |\n",
    "| 1 | | | | | | |\n",
    "| 5 | | | | | | |\n",
    "| 10 | | | | | | |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 3 — Comprendre l'erreur\n",
    "\n",
    "**🎯 Objectif :** Relier l'erreur affichée (la « fonction de perte », *loss*) à la formule de l'entropie croisée, calculée à la main.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5), reste sur **🔥 Jour ouvré, 8 h (apprentissage)** et ouvre **ⓘ Comprendre l'erreur**.\n",
    "2. Note le score de la bonne classe et l'erreur indiqués dans le panneau, au cycle 0.\n",
    "3. Clique sur « 1 cycle » 10 fois : note les mêmes valeurs. Clique ensuite sur **« Apprendre »** (50 cycles de plus) et note-les encore.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Dans la cellule de code, calcule `-np.log(p)` pour les trois valeurs de p lues dans la page (en nombre entre 0 et 1, pas en %). Retrouves-tu l'erreur du panneau (à 0,001 près) ?\n",
    "- L'erreur de cette situation au cycle 0 est-elle plus petite ou plus grande que l'erreur moyenne affichée en haut ? Et que `np.log(3)` ?\n",
    "- Quand le réseau est très sûr de la bonne réponse, vers quelle valeur tend l'erreur ? Et quand il est très sûr de la mauvaise ?"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "p_cycle0 = 0.5   # remplace par le score lu dans la page (entre 0 et 1)\n",
    "p_cycle10 = 0.5\n",
    "p_cycle60 = 0.5\n",
    "for p in (p_cycle0, p_cycle10, p_cycle60):\n",
    "    print(round(-np.log(p), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# Partie B — Comprendre ce que fait le réseau\n",
    "_Ouvrir la « boîte » : résultats détaillés, calcul d'un neurone, rôle des poids._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 4 — Après l'apprentissage : qui réussit, qui échoue ?\n",
    "\n",
    "**🎯 Objectif :** Lire un tableau de résultats sur des situations jamais vues et identifier ce qui manque aux données.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5) et clique sur **« Apprendre »** (50 cycles). Attends la fin.\n",
    "2. Ouvre **🛠️ Mode avancé** puis **« 🧪 Afficher les 8 données de test »**.\n",
    "3. Dans le menu « Tester », sélectionne la situation mal classée et lis ses valeurs.\n",
    "4. Affiche aussi **« 📘 Afficher les 9 données d'apprentissage »** et repère les exemples de week-end.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Combien d'exemples de test sont bien classés ? Quelle est la « Réussite test » affichée ?\n",
    "- Quelle situation est mal classée ? Que répond le réseau, et quel score donne-t-il à la bonne classe ?\n",
    "- Parmi les exemples d'apprentissage de week-end, y en a-t-il un avec une forte affluence ? Qu'en déduis-tu sur ce que le réseau a pu apprendre ?\n",
    "- Quelle autre situation de test est bien classée, mais avec un score faible ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 5 — Ouvrir des neurones cachés\n",
    "\n",
    "**🎯 Objectif :** Refaire à la main le calcul d'un neurone (somme pondérée, biais, sigmoïde) et interpréter ce qu'il « détecte ».\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5), clique sur **« Apprendre »**, et reste sur **🔥 Jour ouvré, 8 h (apprentissage)**.\n",
    "2. Ouvre **🔍 Explorer le calcul**, clique sur **« 🔍 Inspecter un neurone »**, puis sur le **Neurone 3** (le 3ᵉ cercle en partant du haut, dans la colonne du milieu). Lis le calcul : les 5 produits, le biais, la somme z et la sigmoïde. Ce calcul, fait des entrées vers les sorties, s'appelle la **propagation avant** (question 11).\n",
    "3. Fais de même avec le **Neurone 1** (le 1ᵉʳ cercle de la colonne du milieu).\n",
    "4. Ouvre **Mode avancé → « Comprendre les 4 neurones cachés »** et lis les poids de chaque neurone.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Recopie la somme pondérée z du Neurone 3. Dans la cellule de code, vérifie la sigmoïde 1 / (1 + e^(−z)). Retrouves-tu la valeur du cercle ?\n",
    "- Fais la même vérification pour le Neurone 1. Pourquoi est-il peu allumé pour cette situation ? Appuie-toi sur les signes de ses poids.\n",
    "- Décris en quelques mots la situation que « détecte » chacun de ces deux neurones (regarde le signe de leurs poids sur les 5 informations).\n",
    "- Pour quel niveau d'affluence chaque neurone « vote »-t-il ? Utilise les poids vers les trois sorties."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "z = 0   # remplace par la somme pondérée z lue dans la page\n",
    "1 / (1 + np.exp(-z))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 6 — Modifier un poids : voter pour ou contre\n",
    "\n",
    "**🎯 Objectif :** Comprendre qu'un poids est une voix, et que son effet dépend de l'activation du neurone.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5), clique sur **« Apprendre »**, reste sur **🔥 Jour ouvré, 8 h (apprentissage)** et note les 3 scores.\n",
    "2. Ouvre **🔍 Explorer le calcul → « 🎚️ Modifier un poids »**. Dans « Choisir une connexion », prends **De : Neurone 3 → Vers : 🔥 Forte affluence**, puis « Sélectionner ce poids ».\n",
    "3. Déplace le curseur : mets le poids à 0, puis à −5. Note les scores à chaque fois. Clique sur « Restaurer ce poids ».\n",
    "4. Recommence pour les connexions **Neurone 1, 2 et 4 → 🔥**, en mettant à chaque fois le poids à 0 : complète le tableau.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Que deviennent les scores quand tu mets à 0 le poids Neurone 3 → 🔥 ? Et à −5 ? Le réseau change-t-il de réponse ?\n",
    "- Complète le tableau : quel neurone, une fois son poids mis à 0, change le plus le score de 🔥 ? Quel est celui qui change le moins ?\n",
    "- Explique avec la formule contribution = activation × poids. Complète : « un poids positif ____ pour la classe, un poids négatif ____ ; l'effet est proportionnel à ____ »"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**📋 À compléter :**\n",
    "\n",
    "| Poids modifié | Activation du neurone | Poids d'origine | Score 🔥 quand le poids vaut 0 |\n",
    "|---|---|---|---|\n",
    "| Neurone 1 → 🔥 | | | |\n",
    "| Neurone 2 → 🔥 | | | |\n",
    "| Neurone 3 → 🔥 | | | |\n",
    "| Neurone 4 → 🔥 | | | |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# Partie C — Expérimenter\n",
    "_Modifier, comparer, tester les limites._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 7 — Inventer des situations : trouver la frontière, tester l'absurde\n",
    "\n",
    "**🎯 Objectif :** Explorer le comportement du réseau hors des exemples, et en percevoir les limites.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5) et clique sur **« Apprendre »**.\n",
    "2. **Frontière de la pluie** : règle Heure 8 h, jour ouvré, Température 15 °C, Vent 25 km/h. Fais varier **Pluie** de 0 à 10 mm/h et note la réponse du réseau.\n",
    "3. **Midi en semaine** : règle jour ouvré, Température 18 °C, Pluie 0, Vent 20 km/h. Fais varier l'**Heure** de 0 h à 22 h par pas de 2 h et note la réponse.\n",
    "4. **Situations absurdes** : mets tous les curseurs au minimum, lis la réponse ; puis tous au maximum ; puis une tempête à l'heure de pointe (8 h, jour ouvré, 5 °C, 10 mm/h, 60 km/h).\n",
    "\n",
    "**❓ Questions :**\n",
    "- À partir de quelle pluie (en mm/h, pas de 1) le réseau passe-t-il de « forte » à « moyenne » ? Quels sont les scores de part et d'autre ? La faible affluence prend-elle un jour le dessus avec 10 mm/h ?\n",
    "- Que répond le réseau à midi un jour ouvré ? Dans la réalité, y a-t-il une forte affluence à midi ? Pourquoi le réseau ne le sait-il pas ?\n",
    "- Que répond-il pour tout au minimum, tout au maximum, et pour la tempête ? Ces situations existent-elles ? Un score élevé prouve-t-il que le réseau « sait » ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 8 — Le hasard de l'initialisation\n",
    "\n",
    "**🎯 Objectif :** Comparer plusieurs apprentissages avec des départs différents, et distinguer ce qui varie de ce qui reste stable.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5) : note les résultats après **« Apprendre »** (c'est le départ par défaut).\n",
    "2. Clique sur **🎲 Autre départ**, puis sur **« Apprendre »**. Note la réussite apprentissage, la réussite test et l'erreur moyenne apprentissage. Fais-le **3 fois**.\n",
    "3. À chaque essai (y compris le départ par défaut), ouvre **Mode avancé → « Comprendre les 4 neurones cachés »**. Dans chaque carte de neurone, lis la ligne **🔥 Forte** de « Poids vers les trois sorties » et repère le neurone où ce poids est le plus grand.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Complète le tableau (essais 1 à 3).\n",
    "- Qu'est-ce qui est identique d'un essai à l'autre ? Qu'est-ce qui change ?\n",
    "- Le neurone qui vote le plus pour 🔥 a-t-il le même numéro à chaque fois ? Que faut-il en conclure sur le « rôle » d'un neurone ?\n",
    "- Compare tes résultats avec ceux de ton voisin. Est-ce normal qu'ils diffèrent ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**📋 À compléter :**\n",
    "\n",
    "| Essai | Réussite apprentissage | Réussite test | Erreur moyenne apprentissage | N° du neurone qui vote le plus pour 🔥 |\n",
    "|---|---|---|---|---|\n",
    "| Départ par défaut | | | | |\n",
    "| 1 | | | | |\n",
    "| 2 | | | | |\n",
    "| 3 | | | | |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 9 — Combien de neurones cachés ?\n",
    "\n",
    "**🎯 Objectif :** Comparer trois architectures dans les mêmes conditions et lire ce qui change (et ce qui ne change pas).\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5) pour retrouver le départ par défaut.\n",
    "2. Choisis **2 neurones** dans « Neurones cachés ». Clique 5 fois sur **« 1 cycle »** : note l'erreur et la réussite apprentissage. Clique encore 5 fois (10 cycles) : note-les. Clique enfin sur **« Apprendre »** (60 cycles) : note l'erreur d'apprentissage, la réussite test et l'erreur test.\n",
    "3. Choisis **4 neurones**, puis **8 neurones** (le changement réinitialise l'apprentissage et garde le même départ) et recommence.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Complète le tableau.\n",
    "- Quelle architecture apprend le plus vite (erreur d'apprentissage) ? La différence est-elle grande après 5 cycles ?\n",
    "- Au bout de 60 cycles, quelle architecture a l'erreur de test la plus basse ? Peut-on en conclure que « plus de neurones = mieux » ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**📋 À compléter :**\n",
    "\n",
    "| Neurones cachés | Erreur à 5 cycles | Réussite appr. à 5 cycles | Erreur à 10 cycles | Réussite appr. à 10 cycles | Erreur appr. à 60 cycles | Réussite test à 60 cycles | Erreur test à 60 cycles |\n",
    "|---|---|---|---|---|---|---|---|\n",
    "| 2 | | | | | | | |\n",
    "| 4 | | | | | | | |\n",
    "| 8 | | | | | | | |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# Partie D — Garder l'esprit critique\n",
    "_Que peut-on réellement affirmer ?_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 10 — Esprit critique : apprendre plus, est-ce toujours mieux ?\n",
    "\n",
    "**🎯 Objectif :** Constater que l'erreur de test peut remonter alors que l'erreur d'apprentissage baisse, et se demander si un réseau est vraiment nécessaire.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5). Ouvre **Mode avancé → « 🧪 Afficher les 8 données de test »** (laisse-le ouvert).\n",
    "2. Clique 10 fois sur **« 1 cycle »** : note l'erreur d'apprentissage, l'erreur test, la réussite test et le score de la forte affluence pour la ligne « Jour ouvré, 8 h, 15 °C, 2,0 mm/h » (petite pluie).\n",
    "3. Clique sur **« Apprendre »** (60 cycles au total) et note les mêmes valeurs. Clique une seconde fois sur « Apprendre » (110 cycles) et note-les encore.\n",
    "4. Regarde les deux courbes d'erreur (apprentissage en bleu, test en orange) : que fait la courbe orange à la fin ?\n",
    "\n",
    "**❓ Questions :**\n",
    "- Complète le tableau.\n",
    "- Entre 60 et 110 cycles, que fait l'erreur d'apprentissage ? Et l'erreur de test ? Le réseau s'améliore-t-il vraiment ? (Indice : on parle de **surapprentissage**, ou *overfitting*.)\n",
    "- Que devient le score de la forte affluence pour la situation « petite pluie » ? Propose une explication en regardant les exemples d'apprentissage.\n",
    "- Propose deux façons de limiter ce phénomène.\n",
    "- Écris en une phrase une **règle simple à la main** (« si … alors … ») qui prévoirait l'affluence. Dans quel cas un réseau de neurones devient-il préférable à ta règle ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**📋 À compléter :**\n",
    "\n",
    "| Cycle | Erreur apprentissage | Erreur test | Réussite test | Score 🔥 pour « Jour ouvré, 8 h, petite pluie » (tableau de test) |\n",
    "|---|---|---|---|---|\n",
    "| 10 | | | | |\n",
    "| 60 | | | | |\n",
    "| 110 | | | | |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "# Partie E — Sous le capot : le vocabulaire de l'apprentissage\n",
    "_Refaire le calcul en Python, corriger un poids à la main, régler l'apprentissage, comprendre la normalisation._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 11 — La propagation avant, en Python\n",
    "\n",
    "**🎯 Objectif :** Refaire en Python le trajet « entrées → neurones cachés → scores » et reconnaître chaque étape dans le code.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Recharge la page (F5) : cycle 0, situation **🔥 Jour ouvré, 8 h (apprentissage)**. Note les 3 scores.\n",
    "2. Dans ce notebook, exécute la cellule « moteur » ci-dessous (elle contient le réseau écrit en Python, **ne la modifie pas**), puis la cellule suivante.\n",
    "3. Dans la page, ouvre **🔍 Explorer le calcul → 🔍 Inspecter un neurone** et lis la valeur des 4 neurones cachés (cercles de la colonne du milieu).\n",
    "\n",
    "**❓ Questions :**\n",
    "- Les 3 scores calculés en Python sont-ils identiques à ceux de la page ? Et les 4 activations `a` : retrouves-tu les valeurs des cercles ?\n",
    "- Dans la fonction `propagation_avant`, quelle ligne correspond à « somme pondérée + biais puis sigmoïde » ? Quelle ligne correspond au softmax ?\n",
    "- Pourquoi appelle-t-on ce calcul la **propagation avant** (*forward pass*) ? Dans quel sens l'information circule-t-elle ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**⚙️ Cellule à exécuter d'abord (ne pas modifier) :**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Les 9 exemples d'apprentissage et les 8 exemples de test de la page (valeurs déjà normalisées entre 0 et 1)\n",
    "# colonnes : heure, jour ouvré (1) / week-end (0), température, pluie, vent\n",
    "X_app = np.array([[0.333, 1, 0.4, 0, 0.283],\n",
    "                  [0.75, 1, 0.629, 0, 0.333],\n",
    "                  [0.708, 1, 0.743, 0.03, 0.25],\n",
    "                  [0.625, 0, 0.571, 0, 0.333],\n",
    "                  [0.333, 1, 0.343, 0.4, 0.667],\n",
    "                  [0.458, 0, 0.486, 0, 0.25],\n",
    "                  [0.125, 1, 0.343, 0, 0.167],\n",
    "                  [0.042, 1, 0.514, 0, 0.25],\n",
    "                  [0.417, 0, 0.257, 0.6, 0.583]])\n",
    "y_app = np.array([2, 2, 2, 1, 1, 1, 0, 0, 0])          # 0 = faible, 1 = moyenne, 2 = forte\n",
    "X_test = np.array([[0.333, 1, 0.457, 0, 0.333],\n",
    "                  [0.708, 1, 0.571, 0, 0.333],\n",
    "                  [0.583, 0, 0.514, 0, 0.417],\n",
    "                  [0.708, 0, 0.571, 0, 0.333],\n",
    "                  [0.083, 1, 0.371, 0, 0.167],\n",
    "                  [0, 1, 0.4, 0, 0.25],\n",
    "                  [0.333, 1, 0.457, 0.2, 0.417],\n",
    "                  [0.708, 0, 0.8, 0, 0.25]])\n",
    "y_test = np.array([2, 2, 1, 1, 0, 0, 1, 2])\n",
    "noms = ['Faible', 'Moyenne', 'Forte']\n",
    "\n",
    "def depart(n_caches=4, graine=123456):\n",
    "    \"\"\"Poids initiaux tirés au hasard : même générateur que la page, donc même départ par défaut.\"\"\"\n",
    "    etat = [graine]\n",
    "    def hasard():\n",
    "        etat[0] = (1664525 * etat[0] + 1013904223) % 2**32\n",
    "        return etat[0] / 2**32\n",
    "    W1 = np.array([[(hasard() - .5) * 1.4 for _ in range(5)] for _ in range(n_caches)])\n",
    "    W2 = np.array([[(hasard() - .5) * 1.4 for _ in range(n_caches)] for _ in range(3)])\n",
    "    return [W1, np.zeros(n_caches), W2, np.zeros(3)]      # poids et biais des deux couches\n",
    "\n",
    "def sigmoide(z):\n",
    "    return 1 / (1 + np.exp(-np.clip(z, -30, 30)))\n",
    "\n",
    "def softmax(z):\n",
    "    e = np.exp(z - z.max())\n",
    "    return e / e.sum()\n",
    "\n",
    "def propagation_avant(reseau, x):\n",
    "    \"\"\"Fait traverser le réseau à une situation x : renvoie les activations cachées et les 3 scores.\"\"\"\n",
    "    W1, b1, W2, b2 = reseau\n",
    "    a = sigmoide(W1 @ x + b1)         # couche cachée : somme pondérée + biais, puis sigmoïde\n",
    "    p = softmax(W2 @ a + b2)          # sortie : somme pondérée + biais, puis softmax\n",
    "    return a, p\n",
    "\n",
    "def retropropagation(reseau, x, y):\n",
    "    \"\"\"Pour UN exemple : renvoie les gradients (quoi corriger dans chaque poids et chaque biais).\"\"\"\n",
    "    W1, b1, W2, b2 = reseau\n",
    "    a, p = propagation_avant(reseau, x)\n",
    "    ecart_sortie = p.copy()\n",
    "    ecart_sortie[y] -= 1                                  # scores - cible (cible = 1 pour la bonne classe, 0 sinon)\n",
    "    ecart_cache = (W2.T @ ecart_sortie) * a * (1 - a)     # l'écart est renvoyé vers la couche cachée\n",
    "    return [np.outer(ecart_cache, x), ecart_cache, np.outer(ecart_sortie, a), ecart_sortie]\n",
    "\n",
    "def evaluer(reseau, X, y):\n",
    "    \"\"\"Renvoie (nombre d'exemples bien classés, erreur moyenne).\"\"\"\n",
    "    P = [propagation_avant(reseau, x)[1] for x in X]\n",
    "    return sum(int(p.argmax() == c) for p, c in zip(P, y)), float(np.mean([-np.log(p[c]) for p, c in zip(P, y)]))\n",
    "\n",
    "def entrainer(reseau, epoques=5, taille_lot=1, taux=0.18, X=X_app, y=y_app):\n",
    "    \"\"\"Descente de gradient. Renvoie le nombre de mises à jour des poids effectuées.\"\"\"\n",
    "    mises_a_jour = 0\n",
    "    for _ in range(epoques):                                  # une époque = un passage sur tous les exemples\n",
    "        for debut in range(0, len(X), taille_lot):            # un lot = quelques exemples traités ensemble\n",
    "            lot = range(debut, min(debut + taille_lot, len(X)))\n",
    "            grads = [retropropagation(reseau, X[i], y[i]) for i in lot]\n",
    "            for k, w in enumerate(reseau):\n",
    "                w -= taux * sum(g[k] for g in grads) / len(grads)    # un petit pas contre le gradient\n",
    "            mises_a_jour += 1\n",
    "    return mises_a_jour"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "reseau = depart()                                  # poids de départ, identiques à ceux de la page\n",
    "a, p = propagation_avant(reseau, X_app[0])         # X_app[0] = « jour ouvré, 8 h, 14 °C, 0 mm/h, 17 km/h »\n",
    "print('activations des 4 neurones cachés :', np.round(a, 3))\n",
    "print('scores faible / moyenne / forte (%) :', np.round(100 * p, 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 12 — La rétropropagation et la descente de gradient : une mise à jour à la main\n",
    "\n",
    "**🎯 Objectif :** Calculer l'écart de sortie, le gradient d'un poids et son nouveau poids pour un seul exemple, puis comprendre pourquoi la page, après un cycle, donne un résultat différent.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Dans la page (F5, cycle 0), passe la souris sur la connexion **Neurone 3 → 🔥** (entre la colonne du milieu et la sortie) : note le **poids w** et la **valeur transmise**.\n",
    "2. Dans le notebook, complète la cellule (les `...`) en suivant les consignes : écart, gradient, nouveau poids. Exécute-la.\n",
    "3. Dans la page, clique sur **« 1 cycle »**, puis survole à nouveau la même connexion : note le nouveau poids.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Quel est l'écart (`scores − cible`) pour chacune des 3 classes ? Que signifie un écart négatif pour la bonne classe, positif pour les autres ?\n",
    "- Calcule le gradient du poids Neurone 3 → 🔥 et son nouveau poids avec un taux d'apprentissage de 0,18. Le poids augmente-t-il ou diminue-t-il ? Pourquoi est-ce logique ?\n",
    "- Après « 1 cycle », la page donne un autre poids que ton calcul. Pourquoi ? (Combien de mises à jour a subi ce poids pendant un cycle ?)\n",
    "- Dans la dernière cellule, `retropropagation` renvoie aussi l'écart **renvoyé vers les neurones cachés**. Quel neurone caché est le plus corrigé ? Explique avec les mots « **rétropropagation** » et « **descente de gradient** »."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "reseau = depart()\n",
    "x, y = X_app[0], 2                    # situation « jour ouvré, 8 h » ; bonne réponse : forte (classe 2)\n",
    "a, p = propagation_avant(reseau, x)\n",
    "\n",
    "cible = np.array([0, 0, 1])           # 1 pour la bonne classe, 0 pour les autres\n",
    "ecart = ...                           # À COMPLÉTER : scores - cible\n",
    "gradient = ...                        # À COMPLÉTER : écart de la classe Forte × activation du Neurone 3 (a[2])\n",
    "w_avant = reseau[2][2, 2]             # poids Neurone 3 → Forte (ligne = classe, colonne = neurone)\n",
    "taux = 0.18\n",
    "w_apres = ...                         # À COMPLÉTER : w_avant - taux × gradient\n",
    "print('écart :', np.round(ecart, 4))\n",
    "print('gradient :', round(gradient, 4), '| poids :', round(w_avant, 4), '→', round(w_apres, 4))\n",
    "\n",
    "# Écart renvoyé vers les 4 neurones cachés (rétropropagation)\n",
    "g = retropropagation(reseau, x, y)\n",
    "print('écart renvoyé aux neurones cachés :', np.round(g[1], 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 13 — Époque, lot, taux d'apprentissage : les réglages de l'apprentissage\n",
    "\n",
    "**🎯 Objectif :** Comprendre ce que sont une époque, une taille de lot et un taux d'apprentissage en les faisant varier, et distinguer ce que le réseau apprend (poids) de ce que l'on règle (hyperparamètres).\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Exécute la cellule `essai(...)` ci-dessous (elle utilise le moteur de la question 11 et affiche une ligne par essai).\n",
    "2. Lance les 5 premiers essais (tableau A), puis les 5 essais sur le taux d'apprentissage (tableau B). Recopie les résultats dans les tableaux.\n",
    "3. Dans la page (F5), clique 5 fois sur **« 1 cycle »** et compare l'erreur d'apprentissage avec la première ligne du tableau A.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Complète les deux tableaux.\n",
    "- Compare la première ligne du tableau A avec la page après 5 cycles. Que peux-tu en déduire sur le rapport entre un **cycle** de la page et une **époque** ?\n",
    "- Pour 250 époques, compare les tailles de lot 1, 3 et 9. Que se passe-t-il pour le nombre de mises à jour ? Pour l'erreur ? Que faut-il faire avec un lot de 9 pour obtenir une erreur comparable ?\n",
    "- Tableau B : que se passe-t-il avec un taux trop petit ? Trop grand ? Le taux qui donne l'erreur d'apprentissage la plus basse donne-t-il aussi la meilleure erreur de test ?\n",
    "- Classe ces quatre éléments en **paramètres** (appris par le réseau) ou **hyperparamètres** (choisis par la personne qui entraîne) : poids, nombre de neurones cachés, taux d'apprentissage, biais."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**📋 À compléter :**\n",
    "\n",
    "| Époques | Taille du lot | Taux | Mises à jour | Apprentissage (bien classés) | Erreur apprentissage | Test (bien classés) | Erreur test |\n",
    "|---|---|---|---|---|---|---|---|\n",
    "| 25 | 1 | 0,18 | | | | | |\n",
    "| 250 | 1 | 0,18 | | | | | |\n",
    "| 250 | 3 | 0,18 | | | | | |\n",
    "| 250 | 9 | 0,18 | | | | | |\n",
    "| 2500 | 9 | 0,18 | | | | | |\n",
    "\n",
    "**Tableau B** (250 époques, taille de lot 1)\n",
    "\n",
    "| Taux d'apprentissage | Apprentissage (bien classés) | Erreur apprentissage | Test (bien classés) | Erreur test |\n",
    "|---|---|---|---|---|\n",
    "| 0,01 | | | | |\n",
    "| 0,18 | | | | |\n",
    "| 1 | | | | |\n",
    "| 5 | | | | |\n",
    "| 20 | | | | |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**⚙️ Cellule à exécuter d'abord (ne pas modifier) :**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def essai(epoques, taille_lot, taux):\n",
    "    reseau = depart()\n",
    "    n = entrainer(reseau, epoques, taille_lot, taux)\n",
    "    ok, err = evaluer(reseau, X_app, y_app)\n",
    "    ok_t, err_t = evaluer(reseau, X_test, y_test)\n",
    "    print(f'{epoques:5d} époques | lot {taille_lot} | taux {taux:<5} | {n:5d} mises à jour | '\n",
    "          f'apprentissage {ok}/9, erreur {err:.3f} | test {ok_t}/8, erreur {err_t:.3f}')"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Tableau A\n",
    "essai(25, 1, 0.18)\n",
    "essai(250, 1, 0.18)\n",
    "essai(250, 3, 0.18)\n",
    "essai(250, 9, 0.18)\n",
    "essai(2500, 9, 0.18)\n",
    "\n",
    "# Tableau B (250 époques, lot de 1)\n",
    "for taux in (0.01, 0.18, 1, 5, 20):\n",
    "    essai(250, 1, taux)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 14 — Normaliser les entrées : est-ce vraiment utile ?\n",
    "\n",
    "**🎯 Objectif :** Calculer une normalisation à la main, puis mesurer ce qui se passe quand on l'oublie.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Dans la page, ouvre **🛠️ Mode avancé → « 📏 Pourquoi et comment les valeurs sont normalisées ? »** et lis le tableau pour la situation **🔥 Jour ouvré, 8 h**.\n",
    "2. Dans le notebook, exécute la cellule : elle entraîne le même réseau (mêmes réglages : 250 époques, lot de 1, taux 0,18) une fois avec les valeurs normalisées, une fois avec les valeurs brutes (heure en h, température en °C, pluie en mm/h, vent en km/h), et une fois avec les valeurs brutes et un taux de 0,01.\n",
    "\n",
    "**❓ Questions :**\n",
    "- Calcule à la main les 5 valeurs normalisées de « jour ouvré, 8 h, 14 °C, 0 mm/h, 17 km/h » (heure ÷ 24, température ÷ 35, pluie ÷ 10, vent ÷ 60). Retrouves-tu le tableau de la page ?\n",
    "- Compare les trois entraînements (réussite et erreur sur l'apprentissage et sur le test). Que se passe-t-il sans normalisation ?\n",
    "- Explique en une phrase pourquoi une entrée de valeur 17 ou 35 gêne l'apprentissage par rapport à une entrée comprise entre 0 et 1 (indice : regarde ce que devient la sigmoïde pour de grandes valeurs de z)."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "brut_app = np.array([[8, 1, 14, 0, 17], [18, 1, 22, 0, 20], [17, 1, 26, 0.3, 15], [15, 0, 20, 0, 20], [8, 1, 12, 4, 40], [11, 0, 17, 0, 15], [3, 1, 12, 0, 10], [1, 1, 18, 0, 15], [10, 0, 9, 6, 35]])\n",
    "brut_test = np.array([[8, 1, 16, 0, 20], [17, 1, 20, 0, 20], [14, 0, 18, 0, 25], [17, 0, 20, 0, 20], [2, 1, 13, 0, 10], [0, 1, 14, 0, 15], [8, 1, 16, 2, 25], [17, 0, 28, 0, 15]])\n",
    "echelle = np.array([24, 1, 35, 10, 60])      # heure, jour ouvré, température, pluie, vent\n",
    "print('situation 1 normalisée :', np.round(brut_app[0] / echelle, 3))\n",
    "\n",
    "for nom, Xa, Xt, taux in (('valeurs normalisées', X_app, X_test, 0.18), ('valeurs brutes', brut_app, brut_test, 0.18), ('valeurs brutes, taux 0,01', brut_app, brut_test, 0.01)):\n",
    "    reseau = depart()\n",
    "    entrainer(reseau, 250, 1, taux, X=Xa, y=y_app)\n",
    "    print(f'{nom:28s} apprentissage', evaluer(reseau, Xa, y_app), '| test', evaluer(reseau, Xt, y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Question 15 — Synthèse : le vocabulaire en une explication\n",
    "\n",
    "**🎯 Objectif :** Réutiliser le vocabulaire de l'apprentissage pour expliquer, avec tes mots, comment le réseau apprend.\n",
    "\n",
    "**🧪 À faire dans la page :**\n",
    "1. Relis le **glossaire** en haut du notebook et tes réponses aux questions 11 à 14.\n",
    "\n",
    "**❓ Questions :**\n",
    "- En 6 à 8 phrases, explique à quelqu'un qui n'a pas suivi le cours comment le réseau apprend à prévoir l'affluence. Utilise au moins ces mots : **propagation avant, erreur (perte), rétropropagation, gradient, taux d'apprentissage, époque, poids**.\n",
    "- Quelle différence fais-tu entre ce que le réseau **apprend** (paramètres) et ce que **toi tu règles** (hyperparamètres) ? Donne deux exemples de chaque."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Ta réponse\n",
    "\n",
    "_(double-clique ici pour écrire tes observations et ta conclusion)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 🐍 Bonus — Le même réseau en Keras (optionnel)\n",
    "\n",
    "À la partie E, tu as écrit toi-même la boucle d'apprentissage (propagation avant, rétropropagation, descente de gradient) en numpy. Ici, tu construis **le même réseau** (5 → N → 3, sigmoïde puis softmax) avec Keras (`Sequential`, `Dense`, `Adam`), qui fait tout cela en quelques lignes, et tu compares avec ce que tu as observé.\n",
    "\n",
    "⚠️ Keras n'utilise ni le même algorithme (Adam) ni les mêmes réglages que la page : les courbes **ressembleront** à celles de la page, mais les nombres ne seront pas identiques."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cellule 1 — Les données (identiques à celles de la page)**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "from tensorflow.keras import layers\n",
    "\n",
    "# Mêmes situations que la page. Colonnes : heure, jour ouvré (1) / week-end (0), température, pluie, vent.\n",
    "# On normalise comme la page : heure/24, température/35 °C, pluie/10 mm/h, vent/60 km/h.\n",
    "def normaliser(brut):\n",
    "    brut = np.array(brut, dtype=float)\n",
    "    return brut / np.array([24, 1, 35, 10, 60])\n",
    "\n",
    "#                      heure, ouvré, °C, mm/h, km/h\n",
    "X_app = normaliser([[ 8, 1, 14, 0,   17], [18, 1, 22, 0,   20], [17, 1, 26, 0.3, 15],\n",
    "                    [15, 0, 20, 0,   20], [ 8, 1, 12, 4,   40], [11, 0, 17, 0,   15],\n",
    "                    [ 3, 1, 12, 0,   10], [ 1, 1, 18, 0,   15], [10, 0,  9, 6,   35]])\n",
    "y_app = np.array([2, 2, 2, 1, 1, 1, 0, 0, 0])       # 0 = faible, 1 = moyenne, 2 = forte\n",
    "\n",
    "X_test = normaliser([[ 8, 1, 16, 0, 20], [17, 1, 20, 0, 20], [14, 0, 18, 0, 25], [17, 0, 20, 0, 20],\n",
    "                     [ 2, 1, 13, 0, 10], [ 0, 1, 14, 0, 15], [ 8, 1, 16, 2, 25], [17, 0, 28, 0, 15]])\n",
    "y_test = np.array([2, 2, 1, 1, 0, 0, 1, 2])\n",
    "noms = ['Faible', 'Moyenne', 'Forte']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cellule 2 — Le modèle : `Sequential`, `Dense`, `Adam`**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "def construire(n_caches=4, graine=1):\n",
    "    keras.utils.set_random_seed(graine)\n",
    "    modele = keras.Sequential([\n",
    "        keras.Input(shape=(5,)),\n",
    "        layers.Dense(n_caches, activation='sigmoid', name='caches'),   # couche cachée\n",
    "        layers.Dense(3, activation='softmax', name='sortie'),          # 3 scores qui font 100 %\n",
    "    ])\n",
    "    modele.compile(optimizer=keras.optimizers.Adam(learning_rate=0.05),\n",
    "                   loss='sparse_categorical_crossentropy', metrics=['accuracy'])\n",
    "    return modele\n",
    "\n",
    "modele = construire(4)\n",
    "modele.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cellule 3 — Entraînement et courbes**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "histo = modele.fit(X_app, y_app, epochs=100, batch_size=1,\n",
    "                   validation_data=(X_test, y_test), verbose=0)\n",
    "\n",
    "plt.plot(histo.history['loss'], label='apprentissage')\n",
    "plt.plot(histo.history['val_loss'], label='test')\n",
    "plt.xlabel('époque'); plt.ylabel('erreur'); plt.legend(); plt.show()\n",
    "print('Réussite apprentissage :', histo.history['accuracy'][-1])\n",
    "print('Réussite test          :', histo.history['val_accuracy'][-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cellule 4 — Prédictions sur des situations de la page**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "situations = {\n",
    "    'Jour ouvré, 8 h, 14 °C':              [ 8, 1, 14, 0, 17],\n",
    "    'Samedi 17 h, 28 °C (test difficile)': [17, 0, 28, 0, 15],\n",
    "    'Jour ouvré, midi, 18 °C':             [12, 1, 18, 0, 20],\n",
    "    'Tempête à 8 h (5 °C, 10 mm/h, 60 km/h)': [8, 1, 5, 10, 60],\n",
    "}\n",
    "for nom, brut in situations.items():\n",
    "    p = modele.predict(normaliser([brut]), verbose=0)[0]\n",
    "    print(f'{nom:42s}', ' / '.join(f'{n} {100*v:.1f} %' for n, v in zip(noms, p)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cellule 5 — Lire les poids appris**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "W1, b1 = modele.get_layer('caches').get_weights()    # W1 : (5, n_caches)\n",
    "W2, b2 = modele.get_layer('sortie').get_weights()    # W2 : (n_caches, 3)\n",
    "\n",
    "x = normaliser([[8, 1, 14, 0, 17]])[0]               # jour ouvré, 8 h\n",
    "z = x @ W1 + b1                                      # somme pondérée + biais de chaque neurone caché\n",
    "h = 1 / (1 + np.exp(-z))                             # sigmoïde\n",
    "print('activations des neurones cachés :', np.round(h, 3))\n",
    "print('poids de chaque neurone caché vers les sorties (colonnes = faible, moyenne, forte) :')\n",
    "print(np.round(W2, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cellule 6 (pour aller plus loin) — Un vrai jeu de données**"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# 🔭 Pour aller plus loin (non exécuté en préparation) : un vrai jeu de données\n",
    "# « Bike Sharing » (UCI) : https://archive.ics.uci.edu/dataset/275/  -> télécharger hour.csv\n",
    "# Colonnes utiles : hr (heure), workingday (jour ouvré), temp (température normalisée),\n",
    "#                   windspeed (vent normalisé), weathersit (3-4 = pluie ou neige), cnt (locations par heure)\n",
    "import pandas as pd\n",
    "df = pd.read_csv('hour.csv')\n",
    "df['pluie'] = (df['weathersit'] >= 3).astype(float)\n",
    "df['classe'] = pd.qcut(df['cnt'], 3, labels=[0, 1, 2]).astype(int)       # faible / moyenne / forte par tiers\n",
    "X_reel = np.column_stack([df['hr'] / 23, df['workingday'], df['temp'], df['pluie'], df['windspeed']])\n",
    "y_reel = df['classe'].values\n",
    "# À toi : découper en apprentissage (par ex. 2011) et test (2012), entraîner le même réseau, comparer avec une règle simple."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Questions bonus**\n",
    "1. Entraîne le modèle (cellule 3). Les courbes d'apprentissage et de test ressemblent-elles à celles de la page ? Que vaut la réussite sur le test ?\n",
    "2. Lance la cellule de prédictions. Que répond ton modèle pour le **samedi chaud** et pour la **tempête** ? Compare avec la page (questions 4 et 7). Et pour **midi un jour ouvré** ?\n",
    "3. Change `n_caches` (2, 4, 8) dans `construire(...)` et relance l'entraînement. L'effet sur la vitesse est-il le même que dans la question 9 ?\n",
    "4. Avec les poids de la cellule suivante, vérifie à la main que l'activation du premier neurone caché pour « jour ouvré, 8 h » est bien `sigmoïde(x · W1[:, 0] + b1[0])`. Retrouves-tu la logique du neurone vue à la question 5 ?\n",
    "5. Dans la cellule d'entraînement, `epochs=100` et `batch_size=1` sont des hyperparamètres : à quoi correspondent-ils dans la partie E ? Essaie `batch_size=3` puis `batch_size=9` avec le même nombre d'époques : comment évolue la courbe d'erreur ? Et avec `learning_rate` de 0.005 puis 0.5 dans `construire` ?\n",
    "6. *(Pour aller plus loin)* Dans la dernière cellule, charge un **vrai jeu de données de vélos en libre-service** et entraîne le même réseau. Compare avec une règle simple (par exemple « la moyenne par heure et par jour »). Que se passe-t-il avec plusieurs milliers d'exemples au lieu de 9 ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Tes réponses (bonus)\n",
    "\n",
    "_(double-clique ici)_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## 📤 Rendu\n",
    "Enregistre ce notebook (`Fichier > Enregistrer`) et dépose-le avec ton **prénom et ton nom** dans le titre du fichier. Vérifie que **toutes** tes réponses sont écrites et que tes observations sont chiffrées."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}