diff --git a/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9-Compression-Quantization-FP.ipynb b/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9-Compression-Quantization-FP.ipynb index f0e1f9495e..e229e302ef 100644 --- a/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9-Compression-Quantization-FP.ipynb +++ b/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9-Compression-Quantization-FP.ipynb @@ -581,6 +581,14 @@ " f\"{int((minez_rn[bas] != refz[bas]).sum())} ecart(s) en arrondi\")\n" ] }, + { + "cell_type": "markdown", + "id": "c5a91e02", + "metadata": {}, + "source": [ + "**Lecture — le témoin matériel rend un verdict bit à bit, zone dénormalisée comprise.** Sur les 20 000 valeurs du tirage uniforme, l'arrondi reproduit `torch.half()`, `numpy float16` et `torch.bfloat16` **exactement** : `0 ecart(s) exact(s)` sur les trois lignes. Puis le balayage logarithmique place `37856` de ses `40002` valeurs sous le plus petit normal ($2^{-14}$) — la zone où le re-biaisage, la retenue de mantisse et le traitement des dénormalisés peuvent tricher — et l'arrondi y rend encore `0 ecart(s)` : la conversion bit à bit est certifiée par la référence matérielle sur toute la plage, pas seulement sur les cas faciles. Les deux lignes de troncature (`10048 ecart(s)`, puis `18630 ecart(s) -- attendu != 0`) disent l'autre moitié du contrat : la troncature diverge, exactement comme la méthode de la section 5 l'avait annoncé — elle ne se compare pas par égalité, elle se comparera par ses propriétés (P1 à P4, cellule suivante). Le commentaire du code se lit enfin comme une leçon de méthode : un tirage uniforme sur [-4, 4] ne peut **pas** voir la zone dénormalisée (~0,3 valeur attendue sur 20 000) ; un témoin aveugle là où l'implémentation a un trou ne prouve rien — c'est pour cela que le balayage couvre exactement cette zone.\n" + ] + }, { "cell_type": "code", "execution_count": 6, @@ -910,6 +918,14 @@ "print(\"6e-8 tombe dans les denormalises, et 1e-8 est rase a zero.\")" ] }, + { + "cell_type": "markdown", + "id": "d7b26f14", + "metadata": {}, + "source": [ + "**Lecture — le bord bas : la précision meurt en cadence, puis la valeur disparaît.** Lue de haut en bas, la colonne FP16 est une dégradation programmée : `4.043e-04` à `1.000e-03` (l'erreur normale d'arrondi), `1.328e-02` à `1.000e-06`, `1.921e-01` à `1.000e-07`, puis `1.000e+00` — l'erreur totale — quand `1.000e-08` tombe à `0.000e+00` : la valeur est rasée au zéro, elle ne laisse rien derrière elle. Tout ce qui vit sous `2^-14 = 6.104e-05` est déjà dénormalisé : la mantisse perd ses bits de tête les uns après les autres, et l'erreur relative — constante dans les normaux — enfle jusqu'aux `50%` atteints au plancher `2^-24 = 5.960e-08`. La colonne BF16 raconte l'histoire opposée : à `1.000e-08`, elle rend encore `1.001e-08` avec `1.172e-03` d'erreur relative — même exposant que FP32, donc pas de sous-flux accessible à ces échelles. Les dernières lignes de la sortie donnent la règle en clair : `6e-8 tombe dans les denormalises, et 1e-8 est rase a zero`. Le sous-flux n'est pas un bruit de fond : c'est une précision qui s'effondre progressivement, puis une donnée qui disparaît sans préavis — le point exact où FP16 cesse d'être utilisable et où BF16, grossier mais à la plage large, prend le relais.\n" + ] + }, { "cell_type": "code", "execution_count": 10, @@ -1255,6 +1271,14 @@ "print(\"fois -- en virgule flottante, l'ordre des operations change le resultat.\")" ] }, + { + "cell_type": "markdown", + "id": "e9c83a27", + "metadata": {}, + "source": [ + "**Lecture — l'ordre des opérations change le résultat : trois accumulations, quatre ordres de grandeur.** Le même produit rend `2.545e-03` accumulé pas à pas dans la boucle FP16 explicite, `3.255e-04` exécuté par numpy, et `2.975e-07` quand la somme court en FP32 : la seule différence est l'endroit où la somme partielle est réarrondie à chaque pas. La sortie dit d'abord ce que numpy **n'est pas** — ses `3.255e-04` prouvent qu'il n'accumule pas en float16 pur (sinon il serait à `2.545e-03`), mais son écart avec l'accumulation FP32 reste de plusieurs ordres de grandeur : « c'est pourquoi tout accelerateur serieux accumule en FP32 ». La démonstration finale est la plus frappante du notebook : `1.0 + 4096 x 0.0005`, ajouté un à un en FP16, rend `2.0000` contre `3.0480` exact. Chaque `0.0005` est inférieur à la moitié de l'ulp de 1.0 (`2^-11`) : chaque addition l'absorbe, et la somme ne bouge **jamais**. Additionner 4 096 fois un petit nombre n'est pas ajouter une fois un grand nombre — en virgule flottante, le résultat dépend de l'ordre des termes, pas seulement de leur somme. La boucle explicite, elle, réarrondit sa somme partielle cent fois par ligne (cent produits par coefficient) : chaque réarrondi en FP16 laisse un petit résidu, et cent résidus s'additionnent en `2.545e-03` ; l'accumulateur FP32 n'en laisse aucun jusqu'au bout — `2.975e-07`.\n" + ] + }, { "cell_type": "markdown", "id": "b34d46bf", diff --git a/MyIA.AI.Notebooks/ML/DataScienceWithAgents/Track2-GoogleADK/Day6-MLE-Star/Lab14-Ablation-Refinement.ipynb b/MyIA.AI.Notebooks/ML/DataScienceWithAgents/Track2-GoogleADK/Day6-MLE-Star/Lab14-Ablation-Refinement.ipynb index 5a36a42110..ffe5efcb7a 100644 --- a/MyIA.AI.Notebooks/ML/DataScienceWithAgents/Track2-GoogleADK/Day6-MLE-Star/Lab14-Ablation-Refinement.ipynb +++ b/MyIA.AI.Notebooks/ML/DataScienceWithAgents/Track2-GoogleADK/Day6-MLE-Star/Lab14-Ablation-Refinement.ipynb @@ -302,6 +302,14 @@ "print(\"Classe CodeBlockAnalyzer definie : identification de blocs logiques dans un pipeline ML via LLM\")" ] }, + { + "cell_type": "markdown", + "id": "a1f4b7c9", + "metadata": {}, + "source": [ + "**Lecture — le contrat de l'analyseur tient en une ligne de certification, et trois clauses se cachent dedans.** La sortie ne rend qu'une ligne, qui déclare la classe `CodeBlockAnalyzer` et énonce sa mission — « identification de blocs logiques dans un pipeline ML via LLM » — mais ce qu'elle certifie se lit dans la définition qui la précède. Première clause : la classe ne porte qu'un `LLMClient` et une méthode `identify_blocks` — aucune heuristique locale, aucun découpage par mots-clés ; toute l'intelligence du découpage est déléguée au modèle, la classe n'est que le protocole d'interrogation. Deuxième clause, visible dans le prompt : l'extrait envoyé est tronqué à `code[:2000]` caractères, la cardinalité est plafonnée d'avance (« Identifie 3-5 blocs ») et la température est posée à `0.2` — l'identification est stable et reproductible, mais un pipeline plus long que 2 000 caractères serait analysé sur sa seule tête, sans que rien dans la sortie ne l'annonce. Troisième clause, la plus fragile : la réponse est attendue en JSON, extraite par l'expression régulière `\\[.*\\]`, et le `except:` du bloc `try` rend silencieusement une liste **vide** si le modèle délire — ni message d'erreur, ni trace. C'est elle qui donne son sens à la suite : si le regex ne trouve rien, `identify_blocks` rend `[]` et `analyze_and_refine` répond `{'success': False, 'error': 'Impossible d identifier les blocs'}` — le pipeline s'arrête là. Quand la section 6 affichera `- 4 blocs identifies`, ce sera donc déjà un résultat non trivial : le modèle a rendu un tableau JSON bien formé du premier coup, et la tierce clause n'a pas eu à jouer.\n" + ] + }, { "cell_type": "markdown", "id": "cell-7", @@ -397,6 +405,14 @@ "print(\"Classe AblationStudy definie : estimation de l'importance relative des blocs (0.0-1.0) via LLM\")" ] }, + { + "cell_type": "markdown", + "id": "b2e5d8f1", + "metadata": {}, + "source": [ + "**Lecture — l'échelle 0.0–1.0 est une convention du prompt, pas une mesure.** La ligne commise par la classe `AblationStudy` — « estimation de l'importance relative des blocs (0.0-1.0) via LLM » — fixe le vocabulaire de toute la suite du lab. Ces scores ne sortent d'aucun benchmark : ils sont demandés au modèle dans le prompt (« Donne un score d'importance de 0.0 a 1.0 pour chaque bloc »), extraits par le même regex JSON que l'analyseur, puis écrits dans le champ `importance` de chaque `CodeBlock` — le dataclass de la section 2 attendait exactement ce champ (`importance: float = 0.0`), c'est ici qu'il se remplit. La méthode rend les blocs **triés par importance décroissante** (`sorted(blocks, key=lambda b: b.importance, reverse=True)`) : le pipeline de la section 5 pourra prendre `blocks[0]` sans réfléchir, et c'est ce tri qui décide quel bloc sera raffiné. Le détail le plus instructif est dans le `except` : si le JSON ne parse pas, chaque bloc reçoit `0.8 - i * 0.1` — un prior fondé sur l'ordre de découverte, non sur la sémantique ; même en cas d'échec silencieux, la méthode rend donc un classement d'apparence légitime (0.8, 0.7, 0.6…) sans rien avoir mesuré. La sortie de la section 6 rend `Training & Evaluation: importance 0.90`, `Feature Engineering: importance 0.80`, `Model Definition: importance 0.70` et `Import Libraries: importance 0.30` — bien passée par la voie JSON : ses scores ne suivent pas la progression arithmétique du fallback. Mais rien dans le résultat affiché ne permettrait de distinguer les deux chemins a posteriori — c'est la limitation que le resume du lab écrira noir sur blanc : « L'importance est estimee par le LLM (pas testee reellement) ». Au passage, le prompt de la méthode dit aussi ce qu'il demande au modèle : situer le rôle de chaque bloc, pas l'auditer — le descriptif envoyé est tronqué à `b.description[:50]`, assez pour classer, pas assez pour lire le code.\n" + ] + }, { "cell_type": "markdown", "id": "cell-9", @@ -652,6 +668,14 @@ "print(sample_code[:300] + \"...\")" ] }, + { + "cell_type": "markdown", + "id": "c3d6e9a2", + "metadata": {}, + "source": [ + "**Lecture — les 300 caractères affichés portent déjà les quatre blocs que le pipeline va nommer.** La sortie rend le début de l'exemple sous « Code a analyser: » et coupe à `X = df.drop...` : la coupure est le fait du `print(sample_code[:300] + \"...\")`, pas une limite de l'analyseur, qui recevra `code[:2000]`. Chaque fragment visible préfigure un bloc de la section 6. Les trois imports (`pandas`, `RandomForestClassifier`, `cross_val_score`) deviendront « Import Libraries », dernier du classement (importance 0.30) — prérequis mécanique, sans valeur prédictive propre. Les deux lignes de feature engineering — `df['feature_ratio'] = df['feature_a'] / (df['feature_b'] + 1e-6)` puis `df['feature_log'] = np.log1p(df['feature_c'])` — deviendront « Feature Engineering » (0.80) : le `1e-6` du dénominateur maintient le ratio fini quand `feature_b` s'annule, le `log1p` comprime les queues de distribution — deux gestes de robustesse numérique, pas de la décoration. La construction `RandomForestClassifier(n_estimators=100, random_state=42)` deviendra « Model Definition » (0.70), à graine fixée pour rester reproductible. Et c'est l'enchaînement `cross_val_score(model, X, y, cv=5)` — entraînement et évaluation fondues en une seule ligne — qui fera élire « Training & Evaluation » bloc prioritaire (0.90), celui même que le raffinement remplacera par `StratifiedKFold` et `GridSearchCV` en section 7. Relire cet extrait après le pipeline, c'est voir l'ablation se préparer : les blocs ne sont pas inventés par le LLM, ils sont **lus** dans la structure du code — et la qualité des estimations de la section 6 tient tout entière dans ces quatre composants naturels, visibles dès les 300 premiers caractères.\n" + ] + }, { "cell_type": "markdown", "id": "3cdecf9e",