Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Loading