Skip to content
Merged
124 changes: 15 additions & 109 deletions MyIA.AI.Notebooks/RL/PostTraining/RLPT-00e-DPO-TRL-Python.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"\n",
"**Série** Post-Training RL (`rlpt_*`) — niveau intermédiaire · **Prérequis** : [`RLPT-00`](RLPT-00-Reward-Model-Bradley-Terry-Python.ipynb) (le monde synthétique et le reward model Bradley-Terry, repris verbatim), [`RLPT-01`](RLPT-01-PPO-RLHF-Petit-Modele-Python.ipynb) (PPO from scratch), [`RLPT-00e`](RLPT-00e-DPO-TRL-Python.ipynb) (le geste SOTA côté offline, même monde).\n",
"\n",
"Ce notebook ferme l'arc ouvert par [`RLPT-00e`](RLPT-00e-DPO-TRL-Python.ipynb) : l\u00e0 o\u00f9 DPO apprenait\n",
"Ce notebook ferme l'arc ouvert par [`RLPT-00e`](RLPT-00e-DPO-TRL-Python.ipynb) : là où DPO apprenait\n",
"**offline** (un dataset figé de préférences), ici la politique **échantillonne ses propres\n",
"réponses** et apprend en ligne du score d'un juge — l'arc complet RLHF de `RLPT-01`.\n",
"Le geste SOTA est le même : brancher la lib industrielle sur notre monde et départager\n",
Expand Down Expand Up @@ -170,7 +170,7 @@
"tags": []
},
"source": [
"## 1. Le monde synth\u00e9tique (verbatim `RLPT-00`)\n",
"## 1. Le monde synthétique (verbatim `RLPT-00`)\n",
"\n",
"Même monde que `RLPT-00`/`RLPT-00e` : vocabulaire de 8 lettres `a..h` à poids connus,\n",
"deux prompts `<pA>`/`<pB>` avec bonus positionnels conditionnels, réponses de 8 tokens.\n",
Expand Down Expand Up @@ -299,7 +299,7 @@
"## 2. Le juge : reward model Bradley-Terry (recette `RLPT-00`, compressée)\n",
"\n",
"L'arc RLHF complet exige un **reward model appris** — pas la vraie récompense. On\n",
"r\u00e9entra\u00eene le RM de `RLPT-00` : paires de pr\u00e9f\u00e9rences bruit\u00e9es par un Bradley-Terry,\n",
"réentraîne le RM de `RLPT-00` : paires de préférences bruitées par un Bradley-Terry,\n",
"MLE par paires, ~5 s. **Les deux bras (PPO maison et GRPO) optimisent le score de CE\n",
"RM** — le même juge, les mêmes données. La vraie récompense n'intervient qu'aux\n",
"évaluations : c'est là que se mesurera l'écart juge/vrai (la faille de tout RLHF)."
Expand Down Expand Up @@ -514,7 +514,7 @@
"tags": []
},
"source": [
"## 4. Bras maison : PPO-clip avec value net (recette `RLPT-01` transplant\u00e9e)\n",
"## 4. Bras maison : PPO-clip avec value net (recette `RLPT-01` transplantée)\n",
"\n",
"La boucle de `RLPT-01`, adaptée au monde TOK : rollouts masqués au contenu, récompense\n",
"terminale = score RM, **avantage = $r - V(x)$** où $V$ est un value net appris (même\n",
Expand Down Expand Up @@ -661,23 +661,6 @@
" print(f\" it r_rm={h['r']:+.3f} kl={h['kl']:.3f} vloss={h['vloss']:.3f}\")"
]
},
{
"cell_type": "markdown",
"id": "a6441220",
"metadata": {
"papermill": {
"duration": 0.019724,
"end_time": "2026-09-28T08:53:46.812409+00:00",
"exception": false,
"start_time": "2026-09-28T08:53:46.792685+00:00",
"status": "completed"
},
"tags": []
},
"source": [
"**Lecture chiffree — le bras maison en trois iterations : le juge change de camp.** `[Bras maison | PPO clip + value net] 68.0s`, `vraie récompense : 0.219 -> 0.815 (bruit de fond 0.487)`. Les lignes d'iteration imprimees : `it r_rm=-0.184 kl=0.042 vloss=0.826`, puis `it r_rm=+0.606 kl=0.462 vloss=1.216`, puis `it r_rm=+0.769 kl=0.863 vloss=1.526`. Ce que la lecture suivante ne detaille pas (elle traite du calage du value net) : (1) le SIGNE du jugement RM flip entre la premiere et la deuxieme ligne — -0.184 puis +0.606, le juge passe de rejet a approbation en une iteration d'ecart, avant de se rapprocher de son plateau (+0.769) ; (2) la KL accelere puis ralentit — x11 entre les deux premieres lignes (0.042 -> 0.462), puis seulement x1.9 (0.462 -> 0.863) : la derive se stabilise en meme temps que la recompense monte ; (3) la finale 0.815 vaut 1.67 fois le bruit de fond 0.487 — l'alignement est reel mais reste a mi-chemin du plafond glouton 3.20, il y a largement place pour ce que le seed 7 montrera plus bas (0.897) sans jamais l'atteindre.\n"
]
},
{
"cell_type": "markdown",
"id": "d2b5f06d3016",
Expand Down Expand Up @@ -1374,7 +1357,7 @@
"tags": []
},
"source": [
"**Lecture chiffrée — le bras historique, une passe.** Le run complet — 2304 prompts, 12 updates de 192, critic et GAE compris — tient en **14,8 s**, le plus rapide des trois bras : `.train()` garde toute la boucle en batch, et le critic comme le juge sont des GPT-2 mini. La vraie récompense passe de **0.219 à 0.959** (bruit de fond 0.487) : gain **+0.739 au seed 0**, à rapprocher des +0.486 ± 0.205 (maison) et +0.670 ± 0.102 (GRPO) mesurés sur 4 seeds en section 6 — mais seed 0 seul, sans dispersion mesurée. Deux lectures dans le log final (`scores=0.496 kl=0.414 vf_loss=0.294`) : `objective/scores` plafonne autour de 0.50 alors que la vraie récompense atteint 0.96 — le juge BT, entraîné sur des paires, donne une direction, pas une amplitude ; et `loss/value_avg` descend de 0.46 à 0.29 sur les 12 updates : le critic apprend en ligne, c'est la valeur ajoutée structurelle de ce bras par rapport à GRPO."
"**Le bras historique, une passe.** Le run complet — 2304 prompts, 12 updates de 192, critic et GAE compris — tient en **14,8 s**, le plus rapide des trois bras : `.train()` garde toute la boucle en batch, et le critic comme le juge sont des GPT-2 mini. La vraie récompense passe de **0.219 à 0.959** (bruit de fond 0.487) : gain **+0.739 au seed 0**, à rapprocher des +0.486 ± 0.205 (maison) et +0.670 ± 0.102 (GRPO) mesurés sur 4 seeds en section 6 — mais seed 0 seul, sans dispersion mesurée. Deux lectures dans le log final (`scores=0.496 kl=0.414 vf_loss=0.294`) : `objective/scores` plafonne autour de 0.50 alors que la vraie récompense atteint 0.96 — le juge BT, entraîné sur des paires, donne une direction, pas une amplitude ; et `loss/value_avg` descend de 0.46 à 0.29 sur les 12 updates : le critic apprend en ligne, c'est la valeur ajoutée structurelle de ce bras par rapport à GRPO."
]
},
{
Expand Down Expand Up @@ -1479,7 +1462,7 @@
"tags": []
},
"source": [
"**Lecture chiffree — ce que run_seed rejoue, et pourquoi c'est la vraie robustesse.** La fonction relance TOUT le pipeline par graine : nouvelles paires de preferences, nouveau reward model (RM_SEED_LOCAL repose le sien), nouvelles politiques initiales — les barres `Writing model shards` et `Loading weights` (0/28) le rappellent a chaque relance. La consequence methodologique est la difference silencieuse de ce notebook avec un multi-seed ordinaire : la variabilite mesuree a la fin inclut celle du JUGE lui-meme, pas seulement celle de l'optimisation. Deux juges differents peuvent classer differemment les memes reponses ; un gain qui tient sur 4 seeds a juges independants est un gain qui survit a l'incertitude du juge — exactement la propriete qu'un reward model appris doit avoir en production, et que le bruitage Bradley-Terry des paires d'entrainement simule ici a petite echelle.\n"
"**Ce que `run_seed` rejoue, et pourquoi c'est la vraie robustesse.** La fonction relance TOUT le pipeline par graine : nouvelles paires de preferences, nouveau reward model (RM_SEED_LOCAL repose le sien), nouvelles politiques initiales — les barres `Writing model shards` et `Loading weights` (0/28) le rappellent a chaque relance. La consequence methodologique est la difference silencieuse de ce notebook avec un multi-seed ordinaire : la variabilite mesuree a la fin inclut celle du JUGE lui-meme, pas seulement celle de l'optimisation. Deux juges differents peuvent classer differemment les memes reponses ; un gain qui tient sur 4 seeds a juges independants est un gain qui survit a l'incertitude du juge — exactement la propriete qu'un reward model appris doit avoir en production, et que le bruitage Bradley-Terry des paires d'entrainement simule ici a petite echelle.\n"
]
},
{
Expand Down Expand Up @@ -1888,7 +1871,7 @@
"## Références\n",
"\n",
"- `RLPT-00` — le monde synthétique et le reward model Bradley-Terry (même dépôt).\n",
"- `RLPT-01` \u2014 PPO from scratch pour l'alignement d'un LM (m\u00eame d\u00e9p\u00f4t).\n",
"- `RLPT-01` — PPO from scratch pour l'alignement d'un LM (même dépôt).\n",
"- `RLPT-00e` — le geste SOTA offline : `trl.DPOTrainer` sur le même monde (même dépôt).\n",
"- `RLPT-02` / `PT-11a` — GRPO respectivement à la main sur Qwen et à l'échelle avec\n",
" `trl.GRPOTrainer` + rewards vérifiables (même dépôt).\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,23 +711,6 @@
"print(f\"\\nbudget temporel : {t1-t0:.1f}s | gain = {ppo_rewards_curve[-1] - baseline_score:+.3f}\")\n"
]
},
{
"cell_type": "markdown",
"id": "3b009f8c",
"metadata": {
"papermill": {
"duration": 0.005789,
"end_time": "2026-09-28T20:58:04.522982",
"exception": false,
"start_time": "2026-09-28T20:58:04.517193",
"status": "completed"
},
"tags": []
},
"source": [
"**Lecture chiffree -- le troisieme bras en action.** Le `PPOConfig` est instancie (cellule 6) avec les memes hyperparametres que `trl.experimental.ppo.PPOConfig` definit par defaut -- preuve que la surface est REELLEMENT invocable, pas seulement importable. La boucle PPO suit les 4 etapes de Schulman 2017, alignee sur `PPOConfig`. La recompense d'entrainement est le score RM (centre par le bruit de fond implicite) ; l'eval finale est la **vraie recompense** `true_reward` (comme `eval_policy` de `RLPT-00f`)."
]
},
{
"cell_type": "markdown",
"id": "aa7918f2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,26 +527,19 @@
" print(f\" step {s_:>3} : {r_:.3f}\")\n"
]
},
{
"cell_type": "markdown",
"id": "8afce09f",
"metadata": {},
"source": [
"**Lecture chiffree — la courbe de reward en dents de scie.** La serie imprimee : 0.450, 0.400, 0.550, **0.300**, **0.300**, 0.350, 0.550, 0.700. Le creux double aux steps 20-25 AVANT la montee finale — la moyenne de groupe ne monte pas en pente douce, elle touche son minimum apres la moitie du run, puis plus que double (0.300 -> 0.700, soit 2,3x). Et la mesure d'accuracy distingue deux axes que la lecture suivante croise : `Accuracy post-RL (greedy) : 0.88` (inchange, le mode deterministe etait deja acquis) contre `Accuracy post-RL (sampling t=1.0) : 0.62` — la distribution echantillonnee, celle que GRPO explore et note, gagne 0.50 -> 0.62 (+24 % relatifs), pendant que le reward moyen de groupe finit a 0.700.\n"
]
},
{
"cell_type": "markdown",
"id": "4c0e7f1c",
"metadata": {},
"source": [
"### Lecture du résultat\n",
"\n",
"La série de reward moyen imprime ses dents de scie : `0.450`, `0.400`, `0.550`, **`0.300`**, **`0.300`**, `0.350`, `0.550`, `0.700` — creux double aux steps 20-25 AVANT la montée finale, puis plus que double (0.300 -> 0.700, soit 2,3x).\n",
"La lecture honnête de ce run a deux étages. En **greedy**, le modèle était déjà bon : `0.88`\n",
"avant RL, `0.88` après — GRPO n'a rien créé, la réponse déterministe était déjà la bonne. En\n",
"**échantillonnage** (la distribution que GRPO explore et note), la politique s'est concentrée : le reward\n",
"moyen du groupe monte jusqu'à `0.700` sur la courbe, là où la baseline sampling partait de\n",
"`0.50`. C'est l'effet GRPO sur une tâche quasi saturée : les réponses correctes que le modèle\n",
"`0.50` (`0.50 -> 0.62` en accuracy post-RL, +24 % relatifs). C'est l'effet GRPO sur une tâche quasi saturée : les réponses correctes que le modèle\n",
"savait produire deviennent **plus probables**, les erreurs d'échantillonnage sont pénalisées par le groupe.\n",
"\n",
"Deux marqueurs de santé dans les logs du run : `completions/mean_length` ~3 tokens (le modèle répond court et\n",
Expand Down
2 changes: 1 addition & 1 deletion MyIA.AI.Notebooks/RL/RL-04-Bandits-Manchots-Python.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@
"tags": []
},
"source": [
"**Lecture du code — la formule du regret, implementee pas a pas.** La cellule muette (`Fonction run_agent definie`) merite une lecture : c'est ici que la definition mathematique de la section precedente devient executable. `optimal_reward = np.arange(1, n_steps + 1) * bandit.best_prob` construit le front optimal — l'esperance cumulee d'un oracle qui tirerait le meilleur bras a chaque pas — puis `regret = optimal_reward - cumulative_rewards` soustrait le gain reel, pas a pas. Deux consequences visibles dans toutes les sorties qui suivent : le regret est un TRAJET (un vecteur), pas seulement un nombre final — les figures le traceront en pente ; et le denominateur `800.0` des lignes de demonstration est exactement `1000 pas x 0.8`, l'oracle du probleme, pas un maximum arbitraire.\n"
"**La formule du regret, implementee pas a pas.** La cellule muette (`Fonction run_agent definie`) merite une lecture : c'est ici que la definition mathematique de la section precedente devient executable. `optimal_reward = np.arange(1, n_steps + 1) * bandit.best_prob` construit le front optimal — l'esperance cumulee d'un oracle qui tirerait le meilleur bras a chaque pas — puis `regret = optimal_reward - cumulative_rewards` soustrait le gain reel, pas a pas. Deux consequences visibles dans toutes les sorties qui suivent : le regret est un TRAJET (un vecteur), pas seulement un nombre final — les figures le traceront en pente ; et le denominateur `800.0` des lignes de demonstration est exactement `1000 pas x 0.8`, l'oracle du probleme, pas un maximum arbitraire.\n"
]
},
{
Expand Down
20 changes: 6 additions & 14 deletions MyIA.AI.Notebooks/RL/RL-06-DQN-Policy-Gradient-Python.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -845,14 +845,6 @@
" print(f\" {cfg:24s}: {finals.mean():6.1f} (graines : {[f'{v:.0f}' for v in finals]})\")"
]
},
{
"cell_type": "markdown",
"id": "4e8edfaa",
"metadata": {},
"source": [
"**Lecture chiffree — les graines une par une, ce que la moyenne masque.** `Complet (replay + target): 188.5 (graines : ['162', '250', '153'])`, `Sans replay : 256.7 (graines : ['230', '292', '248'])`, `Sans target net : 190.9 (graines : ['115', '237', '221'])`, `Sans les deux : 159.3 (graines : ['142', '141', '195'])`. Les moyennes se resserrent (159 a 257) mais les graines s'etalent bien plus loin (115 a 292) : la meilleure graine de « sans les deux » (195) bat la moyenne du complet. Et le plus bas des graines sans replay (230) ne touche presque pas le plus haut du complet (250). Dernier chiffre de la cellule : 1153 s pour l'ablation entiere, soit 4 configurations x 3 graines = 12 entrainements, environ 96 s l'unite.\n"
]
},
{
"cell_type": "markdown",
"id": "a12518-interp",
Expand All @@ -874,15 +866,15 @@
"\n",
"| Configuration | Moyenne | Graines individuelles |\n",
"|---|---|---|\n",
"| Complet | 188.5 | 188.5 |\n",
"| Sans replay | 256.7 | 256.7 |\n",
"| Sans target net | 190.9 | 190.9 |\n",
"| Sans les deux | 159.3 | 159.3 |\n",
"| Complet | 188.5 | 162, 250, 153 |\n",
"| Sans replay | 256.7 | 230, 292, 248 |\n",
"| Sans target net | 190.9 | 115, 237, 221 |\n",
"| Sans les deux | 159.3 | 142, 141, 195 |\n",
"\n",
"**Le resultat honnete d'abord : sur CartPole, aucune configuration ne diverge.**\n",
"Les quatre variantes apprennent (de 159 a 257), et\n",
"les plages par graine se chevauchent largement — les ecarts entre configurations\n",
"sont **dans le bruit** a ce budget. Pire pour l'intuition : « sans replay » fait\n",
"sont **dans le bruit** a ce budget : la meilleure graine de « sans les deux » (195) bat la moyenne du complet (188.5), et le plus bas des graines sans replay (230) frôle le plus haut du complet (250). Pire pour l'intuition : « sans replay » fait\n",
"ici MIEUX que le complet (257 vs 188), un effet\n",
"connu des taches courtes — des echantillons recents et correles accelerent le\n",
"debut d'apprentissage quand l'horizon est court et l'environnement facile.\n",
Expand All @@ -895,7 +887,7 @@
" deux mecanismes retires ENSEMBLE pesent le plus, mais l'effet reste modeste ici\n",
"3. La variance inter-graines (188 a 257 selon la graine pour une meme\n",
" config) rappelle pourquoi l'ablation multi-graines est obligatoire : sur une\n",
" seule graine, n'importe lequel des 4 classements serait « demontrable »\n",
" seule graine, n'importe lequel des 4 classements serait « demontrable ». L'ablation entière tient en 1153 s — 4 configurations × 3 graines, environ 96 s l'unité\n",
"\n",
"### Les trois ingredients de l'instabilite (la « deadly triad »)\n",
"\n",
Expand Down
29 changes: 1 addition & 28 deletions MyIA.AI.Notebooks/RL/RL-14-RL-Hierarchique-Options-Python.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1288,34 +1288,6 @@
"plt.close()\n"
]
},
{
"cell_type": "markdown",
"id": "5aefb68c",
"metadata": {},
"source": [
"**Lecture des chiffres — pourquoi 0.52 et pas 1.00 ?** Les deux courbes s'achèvent sur le\n",
"même « taux de succès 0.52 », alors que leurs dynamiques n'ont rien en commun. Ce 0.52\n",
"identique est un **artefact de bord**, pas une mesure :\n",
"\n",
"- Le lissage est `np.convolve(..., mode=\"same\")` sur une fenêtre de 60. Au **dernier\n",
" indice**, la fenêtre ne couvre que 31 épisodes réels — le reste est du zéro-padding.\n",
" Un taux brut de 1.00 s'affiche donc ≈ 31/60 ≈ **0.52**. Que les DEUX méthodes affichent\n",
" exactement 0.52 signifie que leurs derniers épisodes sont tous réussis — cohérent avec\n",
" `solved_last100 = 100` mesuré plus haut. Le même artefact déprime le début de chaque\n",
" courbe (bord gauche).\n",
"- **Protocole volontairement différent** du run principal : `ε` **fixe à 0.2** (plus\n",
" d'anneau 0.4 → 0.05), 600 épisodes, 3 seeds (0, 1, 7). La montée du plat est ici plus\n",
" lente que dans le run à `ε` annealing — les courbes se comparent entre elles, pas aux\n",
" médianes précédentes. (Détail : le calcul est une **moyenne** sur les 3 runs, `acc/3`,\n",
" alors que le titre du graphe dit « médiane ».)\n",
"- Le `UserWarning` du backend `Agg` est bénin — il signale juste que la figure n'est pas\n",
" affichée en interactif. La trace quantitative commitée, ce sont les deux valeurs\n",
" imprimées ci-dessus.\n",
"\n",
"À retenir : une moyenne glissante sous-estime systématiquement en bord de série — lire la\n",
"valeur brute ou le last-100 avant de conclure."
]
},
{
"cell_type": "markdown",
"id": "a05905c7",
Expand All @@ -1329,6 +1301,7 @@
"n'ont rien eu à découvrir. La moyenne glissante sur 3 runs lisse la variance : c'est la tendance,\n",
"pas une seed isolée.\n",
"\n",
"Un artefact de bord à connaître avant de lire les valeurs imprimées : les deux courbes s'achèvent sur le même « taux de succès 0.52 » alors que leurs dynamiques n'ont rien en commun. C'est le lissage `np.convolve(..., mode=\"same\")` sur une fenêtre de 60 qui, au dernier indice, ne couvre que ~31 épisodes réels (zéro-padding) : un taux brut de 1.00 s'affiche donc ≈ 31/60 ≈ **0.52** — que les DEUX méthodes affichent exactement 0.52 signifie que leurs derniers épisodes sont tous réussis, cohérent avec `solved_last100 = 100` mesuré plus haut. Protocole volontairement différent du run principal : `ε` fixe à 0.2 (plus d'anneau 0.4 → 0.05), 600 épisodes, 3 seeds — les courbes se comparent entre elles, pas aux médianes précédentes. (Détail : le calcul est une **moyenne** sur les 3 runs, `acc/3`, alors que le titre du graphe dit « médiane » ; le `UserWarning` du backend `Agg` est bénin ; une moyenne glissante sous-estime systématiquement en bord de série — lire la valeur brute ou le last-100 avant de conclure.)\n",
"Encore une fois, lecture honnête : **plus haut ≠ plus appris**. Le créneau des options est la\n",
"signature d'un *planner parfait*, pas d'un *learner*. La courbe qui compte pour l'apprentissage est\n",
"celle de l'intra-option learning, où les sous-politiques partent de zéro.\n"
Expand Down
Loading