diff --git a/MyIA.AI.Notebooks/IIT/ICT-Series/ICT-15g-EmpiricalHuangExploitation.ipynb b/MyIA.AI.Notebooks/IIT/ICT-Series/ICT-15g-EmpiricalHuangExploitation.ipynb index d91ece0c8d..3c397e3a77 100644 --- a/MyIA.AI.Notebooks/IIT/ICT-Series/ICT-15g-EmpiricalHuangExploitation.ipynb +++ b/MyIA.AI.Notebooks/IIT/ICT-Series/ICT-15g-EmpiricalHuangExploitation.ipynb @@ -5,10 +5,10 @@ "id": "ict-15b-empirical-cadrage", "metadata": { "papermill": { - "duration": 0.001791, - "end_time": "2026-09-01T06:42:01.493086+00:00", + "duration": 0.003228, + "end_time": "2026-09-26T23:44:37.877901+00:00", "exception": false, - "start_time": "2026-09-01T06:42:01.491295+00:00", + "start_time": "2026-09-26T23:44:37.874673+00:00", "status": "completed" }, "tags": [] @@ -65,9 +65,18 @@ { "cell_type": "markdown", "id": "statut-epistemique", - "metadata": {}, + "metadata": { + "papermill": { + "duration": 0.004606, + "end_time": "2026-09-26T23:44:37.887692+00:00", + "exception": false, + "start_time": "2026-09-26T23:44:37.883086+00:00", + "status": "completed" + }, + "tags": [] + }, "source": [ - "> **Statut épistémique** — **Sans verdict à ce jour** : aucune ligne de la [matrice de dissociations](../../../docs/ict/dissociations-matrix.md) ne concerne ce notebook ; son statut épistémique sera porté par la matrice le cas échéant." + "> **Statut épistémique** — Deux strates. (1) Le banc hérité (cellules 2 à 9 : conjecture `s_max ≥ √deg_proxy` sur graphes de transition markoviens) reste une **transposition conjecturale, sans verdict** : ses verdicts par substrat sont des repères d'instrument, pas des théorèmes. (2) Le **banc appris [#17977](https://github.com/jsboige/CoursIA/issues/17977)** (fin de notebook) livre un verdict **FAIL** aux critères **déclarés** (Δs_max, proxy de la quantité de l'issue) : 27/28 paires Δs_max = 0, 1 Δ > 0, 0 Δ < 0 ; le complément post-revue sur le ratio ne détecte pas non plus de déplacement (26 Δratio = 0, 2 > 0, 0 < 0, médiane 0,0). L'analyse suit un **plan déclaré avec amendements de calibration documentés** (graines de calibration disjointes), sans re-seuillage. Sur l'hypercube vrai, la borne de Huang est un théorème vérifié mécaniquement (0/118 violations). Reporté dans la [matrice de dissociations](../../../docs/ict/dissociations-matrix.md), ligne **ICT-15g**." ] }, { @@ -76,16 +85,16 @@ "id": "ict-15b-empirical-imports", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:01.496073Z", - "iopub.status.busy": "2026-09-01T06:42:01.496073Z", - "iopub.status.idle": "2026-09-01T06:42:01.942336Z", - "shell.execute_reply": "2026-09-01T06:42:01.940711Z" + "iopub.execute_input": "2026-09-26T23:44:37.895168Z", + "iopub.status.busy": "2026-09-26T23:44:37.894975Z", + "iopub.status.idle": "2026-09-26T23:44:39.199849Z", + "shell.execute_reply": "2026-09-26T23:44:39.199257Z" }, "papermill": { - "duration": 0.448746, - "end_time": "2026-09-01T06:42:01.943285+00:00", + "duration": 1.308897, + "end_time": "2026-09-26T23:44:39.200390+00:00", "exception": false, - "start_time": "2026-09-01T06:42:01.494539+00:00", + "start_time": "2026-09-26T23:44:37.891493+00:00", "status": "completed" }, "tags": [] @@ -127,16 +136,16 @@ "id": "ict-15b-empirical-sanity", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:01.950896Z", - "iopub.status.busy": "2026-09-01T06:42:01.949889Z", - "iopub.status.idle": "2026-09-01T06:42:01.956710Z", - "shell.execute_reply": "2026-09-01T06:42:01.955565Z" + "iopub.execute_input": "2026-09-26T23:44:39.206682Z", + "iopub.status.busy": "2026-09-26T23:44:39.206455Z", + "iopub.status.idle": "2026-09-26T23:44:39.212510Z", + "shell.execute_reply": "2026-09-26T23:44:39.211742Z" }, "papermill": { - "duration": 0.011361, - "end_time": "2026-09-01T06:42:01.957602+00:00", + "duration": 0.010248, + "end_time": "2026-09-26T23:44:39.213286+00:00", "exception": false, - "start_time": "2026-09-01T06:42:01.946241+00:00", + "start_time": "2026-09-26T23:44:39.203038+00:00", "status": "completed" }, "tags": [] @@ -192,10 +201,10 @@ "id": "ict-15b-empirical-methode", "metadata": { "papermill": { - "duration": 0.002473, - "end_time": "2026-09-01T06:42:01.963056+00:00", + "duration": 0.002361, + "end_time": "2026-09-26T23:44:39.218516+00:00", "exception": false, - "start_time": "2026-09-01T06:42:01.960583+00:00", + "start_time": "2026-09-26T23:44:39.216155+00:00", "status": "completed" }, "tags": [] @@ -228,16 +237,16 @@ "id": "ict-15b-empirical-gray-scott", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:01.969661Z", - "iopub.status.busy": "2026-09-01T06:42:01.969156Z", - "iopub.status.idle": "2026-09-01T06:42:02.051825Z", - "shell.execute_reply": "2026-09-01T06:42:02.050244Z" + "iopub.execute_input": "2026-09-26T23:44:39.224192Z", + "iopub.status.busy": "2026-09-26T23:44:39.223937Z", + "iopub.status.idle": "2026-09-26T23:44:39.298741Z", + "shell.execute_reply": "2026-09-26T23:44:39.298239Z" }, "papermill": { - "duration": 0.087294, - "end_time": "2026-09-01T06:42:02.052646+00:00", + "duration": 0.078406, + "end_time": "2026-09-26T23:44:39.299315+00:00", "exception": false, - "start_time": "2026-09-01T06:42:01.965352+00:00", + "start_time": "2026-09-26T23:44:39.220909+00:00", "status": "completed" }, "tags": [] @@ -285,16 +294,16 @@ "id": "ict-15b-empirical-axelrod", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:02.058337Z", - "iopub.status.busy": "2026-09-01T06:42:02.058337Z", - "iopub.status.idle": "2026-09-01T06:42:02.236338Z", - "shell.execute_reply": "2026-09-01T06:42:02.235748Z" + "iopub.execute_input": "2026-09-26T23:44:39.304916Z", + "iopub.status.busy": "2026-09-26T23:44:39.304760Z", + "iopub.status.idle": "2026-09-26T23:44:39.470030Z", + "shell.execute_reply": "2026-09-26T23:44:39.469628Z" }, "papermill": { - "duration": 0.182635, - "end_time": "2026-09-01T06:42:02.238250+00:00", + "duration": 0.168986, + "end_time": "2026-09-26T23:44:39.470767+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.055615+00:00", + "start_time": "2026-09-26T23:44:39.301781+00:00", "status": "completed" }, "tags": [] @@ -345,16 +354,16 @@ "id": "ict-15b-empirical-grokking", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:02.243368Z", - "iopub.status.busy": "2026-09-01T06:42:02.243368Z", - "iopub.status.idle": "2026-09-01T06:42:02.252040Z", - "shell.execute_reply": "2026-09-01T06:42:02.251448Z" + "iopub.execute_input": "2026-09-26T23:44:39.476172Z", + "iopub.status.busy": "2026-09-26T23:44:39.476026Z", + "iopub.status.idle": "2026-09-26T23:44:39.480130Z", + "shell.execute_reply": "2026-09-26T23:44:39.479711Z" }, "papermill": { - "duration": 0.012387, - "end_time": "2026-09-01T06:42:02.253476+00:00", + "duration": 0.007646, + "end_time": "2026-09-26T23:44:39.480723+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.241089+00:00", + "start_time": "2026-09-26T23:44:39.473077+00:00", "status": "completed" }, "tags": [] @@ -408,16 +417,16 @@ "id": "ict-15b-empirical-may", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:02.261063Z", - "iopub.status.busy": "2026-09-01T06:42:02.260057Z", - "iopub.status.idle": "2026-09-01T06:42:02.283278Z", - "shell.execute_reply": "2026-09-01T06:42:02.282274Z" + "iopub.execute_input": "2026-09-26T23:44:39.486422Z", + "iopub.status.busy": "2026-09-26T23:44:39.486199Z", + "iopub.status.idle": "2026-09-26T23:44:39.499977Z", + "shell.execute_reply": "2026-09-26T23:44:39.499464Z" }, "papermill": { - "duration": 0.027555, - "end_time": "2026-09-01T06:42:02.284315+00:00", + "duration": 0.017501, + "end_time": "2026-09-26T23:44:39.500672+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.256760+00:00", + "start_time": "2026-09-26T23:44:39.483171+00:00", "status": "completed" }, "tags": [] @@ -466,16 +475,16 @@ "id": "ict-15b-empirical-cross-substrat", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:02.291824Z", - "iopub.status.busy": "2026-09-01T06:42:02.290304Z", - "iopub.status.idle": "2026-09-01T06:42:02.314155Z", - "shell.execute_reply": "2026-09-01T06:42:02.313571Z" + "iopub.execute_input": "2026-09-26T23:44:39.506401Z", + "iopub.status.busy": "2026-09-26T23:44:39.506192Z", + "iopub.status.idle": "2026-09-26T23:44:39.527726Z", + "shell.execute_reply": "2026-09-26T23:44:39.527193Z" }, "papermill": { - "duration": 0.02795, - "end_time": "2026-09-01T06:42:02.315459+00:00", + "duration": 0.024972, + "end_time": "2026-09-26T23:44:39.528229+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.287509+00:00", + "start_time": "2026-09-26T23:44:39.503257+00:00", "status": "completed" }, "tags": [] @@ -548,10 +557,10 @@ "id": "ict-15b-empirical-interp-cross", "metadata": { "papermill": { - "duration": 0.002744, - "end_time": "2026-09-01T06:42:02.321334+00:00", + "duration": 0.002699, + "end_time": "2026-09-26T23:44:39.533807+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.318590+00:00", + "start_time": "2026-09-26T23:44:39.531108+00:00", "status": "completed" }, "tags": [] @@ -573,7 +582,817 @@ "- ICT-15d (PR #9334) : verdict `NOISE` cross-substrat pour le **proxy Cech** (sections colineaires par construction, instrument plafonne).\n", "- ICT-15b (ce notebook) : conjecture **binaire** `s_max >= sqrt(deg_proxy)` -- plus discriminante que les deux precedentes car elle donne un verdict **par substrat** et non un verdict agrege.\n", "\n", - "Si ICT-15b donne une majorite `consistent`, c'est un **signal positif** : la sensibilite locale **borne** le proxy polynomial meme hors de l'hypercube. Si majorite `inconsistent`, ICT-15b **refute** la transposition directe -- mais cela ne refute **pas** l'intuition Huang en general, juste sa transposition litterale aux graphes de transition.\n" + "Si ICT-15b donne une majorite `consistent`, c'est un **signal positif** : la sensibilite locale **borne** le proxy polynomial meme hors de l'hypercube. Si majorite `inconsistent`, ICT-15b **refute** la transposition directe -- mais cela ne refute **pas** l'intuition Huang en general, juste sa transposition litterale aux graphes de transition.\n", + "\n", + "## Banc appris (#17977) — où le ratio s_max / √deg sature-t-il sur de petits réseaux appris ?\n", + "\n", + "*See [#17977](https://github.com/jsboige/CoursIA/issues/17977) — Part of [#4588](https://github.com/jsboige/CoursIA/issues/4588), suite de [#7288](https://github.com/jsboige/CoursIA/issues/7288) (ICT-15b) et des réserves mesurées [#9771](https://github.com/jsboige/CoursIA/issues/9771) / [#9764](https://github.com/jsboige/CoursIA/issues/9764).*\n", + "\n", + "Le banc précédent (cellules 2 à 9) transpose Huang sur des **graphes de transition markoviens** — une **transposition conjecturale** : l'hypercube, la régularité du graphe et le degré polynomial y sont remplacés par des proxys. Ce statut est **conservé** pour ce banc legacy et pour la lecture ICT qui en découle. Le présent banc ajoute le chaînon demandé par #17977 : des **réseaux booléens réellement appris**, mesurés sur l'**hypercube vrai** — là où l'inégalité de Huang est un **théorème** (l'instrument est calibré par le théorème ; la question empirique porte ailleurs : la saturation du ratio et le déplacement par entraînement adversarial).\n", + "\n", + "**Question (#17977)** : pour des fonctions booléennes apprises par de petits réseaux, où le ratio s_max / √deg sature-t-il ? L'entraînement adversarial déplace-t-il ce ratio, et dans quel sens ?\n", + "\n", + "### Plan d'analyse déclaré (critères d'échec explicites)\n", + "\n", + "Les définitions, seuils, graines, cibles et prédiction ci-dessous constituent le **plan d'analyse déclaré** du banc : les critères d'échec (P1 réfutée → verdict `FAIL`) sont explicites et n'ont pas été re-seuillés après lecture des résultats. Les corrections d'instrument nécessaires à la mise au point sont **documentées** dans la section « Amendements de calibration » (graines de calibration {11, 22} disjointes des graines de verdict {0, 1, 7, 42}) ; l'assemblage du notebook ne fournit pas d'horodatage indépendant externe de ces étapes — la traçabilité repose sur cette documentation, pas sur une affirmation temporelle invérifiable.\n", + "\n", + "**Encodage pré-défini** (réserve #9771 : le VRAI degré de Fourier, pas un proxy de masse) :\n", + "\n", + "- entrées `b ∈ {0,1}^n`, encodage `x = 1 − 2b ∈ {−1,+1}^n` ;\n", + "- réseau `g : R^n → R` (MLP `n → 16 (tanh) → 1 (linéaire)`), fonction réalisée `f̂(x) = +1 si g(x) ≥ 0, −1 sinon` (table de vérité complète, déterministe) ;\n", + "- coefficients de Fourier-Walsh `f̂(S) = 2^{−n} Σ_x f(x) χ_S(x)` avec `χ_S(x) = Π_{i∈S} x_i` ;\n", + "- **degré réel** : `deg(f) = max{|S| : |f̂(S)| > TOL}` avec `TOL = 1e-9` — les tables sont exactes, les coefficients sont des multiples de `2^{1−n}` : la tolérance stricte est légitime ;\n", + "- **s_max exact sur l'hypercube** `Q_n` : `s_max(f) = max_x |{i : f(x ⊕ e_i) ≠ f(x)}|` — c'est la **définition de Huang**, **distincte** de la sensibilité sur graphe de transition observé (`ict.sensitivity.local_sensitivity`, banc legacy ci-dessus). Les deux grandeurs sont nommées séparément et jamais mélangées.\n", + "\n", + "**Design** :\n", + "\n", + "| Élément | Valeur |\n", + "|---|---|\n", + "| tailles | `n ∈ {3, 4}` (tables de 8 / 16 entrées, CPU borné) |\n", + "| cibles (labels contrôlés) | `OR_n`, `AND_n`, `MAJ_n` (n=3 : ≥2 ; n=4 : ≥3), `PAR_n`, `TRI_bal_n` (table aléatoire équilibrée, graine `20260720 + n`, imprimée dans la sortie) |\n", + "| graines de verdict | `{0, 1, 7, 42}` (≥ 2 exigé) ; par graine : init du réseau et split 75 % train / 25 % holdout déterministes (rng séparés), **identiques entre les deux régimes** (paires appariées) |\n", + "| régimes | `normal` = BCE **équilibrée par classe** sur les points train ; `adversarial` = BCE(train) + **0,5 ×** BCE du **pire voisin Hamming-1** de chaque point train (adversaire discret exact à rayon 1, label du point propre ; λ = 0,5, cf. amendements) |\n", + "| budget | 400 étapes Adam lr 0.05, full batch — budget FIXE, convergence non forcée (c'est l'écart de généralisation qui porte le signal) |\n", + "| garde anti-dégénérescence (régimes entraînés) | entrée marquée `degenerate` si table réalisée constante OU train_acc < 0.9 ; exclue des verdicts, comptée honnêtement (réserve : éviter les réseaux constants vacuous) ; pour le contrôle `untrained`, seule la constance exclut (aucune exigence d'accuracy à 0 étape) |\n", + "\n", + "**Contrôles** :\n", + "\n", + "1. réseau non entraîné (même init que la paire, 0 étape) ;\n", + "2. bande aléatoire appariée en degré : n=3 **exhaustive** (les 256 fonctions booléennes), n=4 échantillon de 3000 fonctions uniformes — distribution de référence du ratio par degré.\n", + "\n", + "**Prédiction falsifiable P1 et sa mesure** :\n", + "\n", + "- **P1** : l'entraînement adversarial **réduit** s_max de la fonction réalisée — critère déclaré sur **Δs_max**, **proxy** de la quantité de l'issue (le ratio s_max/√deg ; le complément Δratio est mesuré en fin de banc, section post-revue). CONFIRMÉE ssi (i) `n_paires ≥ 8` (paires non-dégénérées des deux côtés), (ii) `médiane(Δs_max) < 0`, (iii) `#{Δ < 0} > #{Δ > 0}` (les égalités à 0 ne comptent pour aucun camp).\n", + "- **P2 (gate instrument — théorème, pas prédiction)** : toute table réalisée non constante satisfait `s_max ≥ √deg` (Huang sur Q_n). Violation = bug instrument → verdict `INVALID`.\n", + "- **P3 (contrôle untrained)** : `médiane(s_max_norm) ≤ médiane(s_max_untrained)` — l'entraînement sur labels contrôlés n'augmente pas la sensibilité médiane.\n", + "- **P4 (résolution / saturation, réserves #9771-#9764)** : au moins **2 degrés distincts** parmi les tables réalisées non-dégénérées des régimes entraînés, ET fraction des entrées entraînées (non-dégénérées) au plafond structurel `s_max = n` **strictement inférieure à 3/4** (la mesure ne sature pas).\n", + "\n", + "**Verdict global (critères déclarés)** :\n", + "\n", + "| Condition | Verdict |\n", + "|---|---|\n", + "| P2 violée ou gates instrument en échec | `INVALID` |\n", + "| P4 non tenue, ou n_paires < 8, ou fraction de dégénérés > 1/3 (régimes entraînés) | `INCONCLUSIVE` |\n", + "| P1 confirmée (hors régime INCONCLUSIVE) | `PASS` |\n", + "| P1 réfutée (`médiane(Δ) ≥ 0` ou direction inversée) hors régime INCONCLUSIVE | `FAIL` |\n", + "\n", + "Le verdict porte sur P1 (déplacement par l'adversarial, sens prédit : vers le bas) ; P3 et P4 sont des contrôles rapportés, pas des conditions du verdict.\n", + "\n", + "### Amendements de calibration (documentés ; graines de calibration {11, 22} disjointes des graines de verdict {0, 1, 7, 42})\n", + "\n", + "Trois corrections d'**instrument**, appliquées en utilisant **uniquement** les graines {11, 22} — disjointes des graines de verdict — avec pour seul critère la **santé de l'instrument** (taux de dégénérés, train_acc du régime normal), pas le signal Δs_max ou Δratio. Les seuils de verdict (P1-P4, bornes, graines de verdict, garde anti-dégénérescence) sont restés inchangés du plan initial à l'analyse.\n", + "\n", + "1. **Gradient BCE (v1 → v2)** : la première version codait `dL/dz = −y·σ(y·z)` au lieu de `−y·σ(−y·z)` — un gradient de signe faut qui plafonnait train_acc vers 0,5-0,85 en rendant la confiance répulsive. Corrigé + gate optimiseur ajoutée (fit parfait de MAJ_3 sur table complète, graine 11).\n", + "2. **BCE équilibrée par classe (v2)** : poids `1/(2·fréquence de classe)` normalisés — sans elle, les cibles déséquilibrées (OR/AND : `2^n − 1` contre 1) convergent vers la constante vacuous, précisément ce que la garde anti-dégénérescence doit éviter de produire en masse.\n", + "3. **Poids adversarial λ = 0,5 (v3)** : à rayon 1 sur l'hypercube, le pire voisin Hamming-1 d'un point porte souvent le label **opposé** (toujours, pour PAR) ; avec un poids 1/1, le terme adversarial contredit le terme propre au point de bloquer l'apprentissage — λ = 1 a été écarté à ce stade de calibration (graines disjointes {11, 22}), sans que le taux de dégénérés mesuré alors soit conservé dans une sortie committée. λ = 0,5 (poids à la TRADES) a été retenu : il conserve la pression de lissage local tout en laissant l'apprentissage converger (le gate optimiseur de la cellule instrument, exécuté dans le notebook, exige train_acc ≥ 0,9 en régime adversarial λ = 0,5). **Grille λ testée : {0 (= normal), 0,5, 1}** — aucune valeur intermédiaire entre 0,5 et 1 n'a été essayée." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ae365629", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-26T23:44:39.540420Z", + "iopub.status.busy": "2026-09-26T23:44:39.540213Z", + "iopub.status.idle": "2026-09-26T23:44:39.674832Z", + "shell.execute_reply": "2026-09-26T23:44:39.674234Z" + }, + "id": "ict-15g-learned-instrument", + "papermill": { + "duration": 0.138641, + "end_time": "2026-09-26T23:44:39.675375+00:00", + "exception": false, + "start_time": "2026-09-26T23:44:39.536734+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Gates instrument (valeurs de reference) ===\n", + " dictateur_4 : deg=1 (attendu 1), s_max=1 (attendu 1) -> OK\n", + " OR_4 : deg=4 (attendu 4), s_max=4 (attendu 4) -> OK\n", + " MAJ_3 : deg=3 (attendu 3), s_max=2 (attendu 2) -> OK\n", + " PAR_4 : deg=4 (attendu 4), s_max=4 (attendu 4) -> OK\n", + " theoreme Huang verifie exhaustivement sur les 256 fonctions de n=3 : 0 violation(s)\n", + " gate optimiseur (MAJ_3 complet, graine 11 disjointe) : train_acc normal = 1.000, adversarial (lam=0.5) = 1.000 (attendus 1.0 / >= 0.9)\n", + "Gates instrument PASS : l instrument discrimine (deg et s_max exacts), respecte Huang sur n=3, et l optimiseur converge (normal ET adversarial lam=0.5).\n" + ] + } + ], + "source": [ + "# --- Banc #17977 : instrument -- degre de Fourier REEL + sensibilite hypercube exacte ---\n", + "# Reserve #9771 : le degre mesure ici est le VRAI degre de Fourier-Walsh de la table de\n", + "# verite complete, pas un proxy de masse de probabilite.\n", + "# Reserve #9764 : la saturation de la mesure est testee explicitement (gate P4) avant\n", + "# tout verdict. numpy pur, CPU borne, deterministe (rng explicites, aucun alea cache).\n", + "import itertools\n", + "\n", + "TOL_FOURIER = 1e-9 # pre-enregistre : coefficients multiples de 2^{1-n}, tolerance stricte legitime\n", + "\n", + "\n", + "def hypercube_inputs(n):\n", + " \"\"\"Encodage pre-defini : b in {0,1}^n -> x = 1 - 2*b in {-1,+1}^n (ordre lexicographique).\"\"\"\n", + " B = np.array(list(itertools.product([0, 1], repeat=n)), dtype=float)\n", + " return 1.0 - 2.0 * B\n", + "\n", + "\n", + "def fourier_degree(table_pm1, n):\n", + " \"\"\"Degre de Fourier-Walsh REEL de f : {-1,+1}^n -> {-1,+1} (table sur l'hypercube ordonne).\n", + "\n", + " f_hat(S) = 2^{-n} * sum_x f(x) * chi_S(x), chi_S(x) = prod_{i in S} x_i.\n", + " deg(f) = max{ |S| : |f_hat(S)| > TOL_FOURIER }. Constante -> deg = 0.\n", + " \"\"\"\n", + " X = hypercube_inputs(n)\n", + " deg = 0\n", + " for k in range(n + 1):\n", + " for S in itertools.combinations(range(n), k):\n", + " chi = np.prod(X[:, list(S)], axis=1) if S else np.ones(len(X))\n", + " if abs(float(np.mean(table_pm1 * chi))) > TOL_FOURIER:\n", + " deg = max(deg, k)\n", + " return deg\n", + "\n", + "\n", + "def hypercube_sensitivity(table_pm1, n):\n", + " \"\"\"Sensibilite EXACTE s_x(f) sur l'hypercube Q_n (definition de Huang).\n", + "\n", + " Distincte de la sensibilite sur graphe de transition observe (banc legacy,\n", + " ict.sensitivity.local_sensitivity) : ici le voisinage est TOUJOURS l'ensemble\n", + " des n voisins Hamming-1 de l'hypercube, sans observation ni lissage.\n", + " \"\"\"\n", + " X = hypercube_inputs(n)\n", + " index = {tuple(x): i for i, x in enumerate(X)}\n", + " s = np.zeros(len(X), dtype=int)\n", + " for i, x in enumerate(X):\n", + " flips = 0\n", + " for j in range(n):\n", + " x_nb = x.copy()\n", + " x_nb[j] = -x_nb[j]\n", + " flips += int(table_pm1[index[tuple(x_nb)]] != table_pm1[i])\n", + " s[i] = flips\n", + " return s\n", + "\n", + "\n", + "# --- Petit MLP numpy (CPU borne, deterministe) ---\n", + "class TinyMLP:\n", + " \"\"\"MLP n -> hidden(tanh) -> 1 (lineaire), gradient manuel, Adam externe.\"\"\"\n", + "\n", + " def __init__(self, n, hidden=16, seed=None):\n", + " r = np.random.default_rng(seed)\n", + " self.W1 = r.uniform(-1.0, 1.0, size=(n, hidden)) / np.sqrt(n)\n", + " self.b1 = np.zeros(hidden)\n", + " self.W2 = r.uniform(-1.0, 1.0, size=hidden) / np.sqrt(hidden)\n", + " self.b2 = np.zeros(1)\n", + " self._X = None\n", + " self._h = None\n", + "\n", + " def forward(self, X):\n", + " self._X = X\n", + " self._h = np.tanh(X @ self.W1 + self.b1)\n", + " return self._h @ self.W2 + self.b2[0]\n", + "\n", + " def params(self):\n", + " return [self.W1, self.b1, self.W2, self.b2]\n", + "\n", + " def backward(self, dlogit):\n", + " gW2 = self._h.T @ dlogit\n", + " gb2 = np.array([dlogit.sum()])\n", + " dh = np.outer(dlogit, self.W2) * (1.0 - self._h ** 2)\n", + " gW1 = self._X.T @ dh\n", + " gb1 = dh.sum(axis=0)\n", + " return [gW1, gb1, gW2, gb2]\n", + "\n", + "\n", + "def _sigmoid(v):\n", + " return 0.5 * (1.0 + np.tanh(0.5 * v)) # stable\n", + "\n", + "\n", + "def bce_with_logits(z, y, w=None):\n", + " \"\"\"BCE moyenne avec logits, y in {-1,+1} : L = mean softplus(-y*z).\n", + "\n", + " w : poids optionnels par point (normalises, moyenne 1) -- ponderation equilibree\n", + " par classe pour eviter que les cibles desequilibrees (OR/AND : 2^n - 1 contre 1)\n", + " ne convergent vers la constante vacuous (amendement pre-execution v2).\n", + " dL/dz_i = -y_i * sigma(-y_i*z_i) * w_i / m (sigma(-u) = 1 - sigma(u)).\n", + " \"\"\"\n", + " u = -y * z\n", + " if w is None:\n", + " w = np.ones_like(z)\n", + " loss = float(np.mean(w * np.logaddexp(0.0, u)))\n", + " dz = -(w * y * _sigmoid(u)) / len(z)\n", + " return loss, dz\n", + "\n", + "\n", + "def _class_balance_weights(ytr):\n", + " \"\"\"Poids 1/(2*freq_classe), normalises a la moyenne 1 (ponderation equilibree).\"\"\"\n", + " frac_pos = float(np.mean(ytr > 0))\n", + " w = np.where(ytr > 0, 0.5 / max(frac_pos, 1e-9), 0.5 / max(1.0 - frac_pos, 1e-9))\n", + " return w / w.mean()\n", + "\n", + "\n", + "def train_mlp(n, Xtr, ytr, *, regime, seed, steps=400, lr=0.05, lam=0.5):\n", + " \"\"\"Entraine un TinyMLP ; regime in {'normal', 'adversarial', 'untrained'}.\n", + "\n", + " normal : L = BCE equilibree(g(Xtr), y).\n", + " adversarial : L = BCE equilibree(g(Xtr), y) + lam * BCE equilibree(g(x_adv), y)\n", + " ou x_adv est le pire voisin Hamming-1 de chaque point train\n", + " (adversaire discret exact a rayon 1, label du point propre).\n", + " lam = 0.5 (amendement pre-execution v3) : a rayon 1 sur l'hypercube,\n", + " le pire voisin porte souvent le label oppose (toujours pour PAR) ;\n", + " lam = 1 a ete ecarte en calibration (graines disjointes 11/22) :\n", + " a ce poids le terme adversarial bloque l'apprentissage (taux de\n", + " degeneres non conserve dans une sortie committee) ; lam = 0.5\n", + " (poids a la TRADES) restaure l'apprentissage en conservant la\n", + " pression de lissage local.\n", + " untrained : reseau a l'initialisation (controle, 0 etape).\n", + " \"\"\"\n", + " net = TinyMLP(n, seed=seed)\n", + " if regime == 'untrained':\n", + " return net\n", + " w = _class_balance_weights(ytr)\n", + " params = net.params()\n", + " m_a = [np.zeros_like(p) for p in params]\n", + " v_a = [np.zeros_like(p) for p in params]\n", + " beta1, beta2, eps = 0.9, 0.999, 1e-8\n", + " for t in range(1, steps + 1):\n", + " z = net.forward(Xtr)\n", + " _, dl = bce_with_logits(z, ytr, w)\n", + " grads = net.backward(dl)\n", + " if regime == 'adversarial':\n", + " Xnb = np.repeat(Xtr[:, None, :], n, axis=1).copy()\n", + " jj = np.arange(n)\n", + " Xnb[:, jj, jj] *= -1.0 # Xnb[i, j] = Xtr[i] avec coord j basculee\n", + " znb = net.forward(Xnb.reshape(-1, n)).reshape(len(Xtr), n)\n", + " losses_nb = np.logaddexp(0.0, -ytr[:, None] * znb)\n", + " worst = np.argmax(losses_nb, axis=1)\n", + " X_adv = Xnb[np.arange(len(Xtr)), worst]\n", + " z_adv = net.forward(X_adv)\n", + " _, dl_adv = bce_with_logits(z_adv, ytr, w)\n", + " g_adv = net.backward(dl_adv * lam)\n", + " grads = [g + ga for g, ga in zip(grads, g_adv)]\n", + " for k, (p, g) in enumerate(zip(params, grads)):\n", + " m_a[k] = beta1 * m_a[k] + (1.0 - beta1) * g\n", + " v_a[k] = beta2 * v_a[k] + (1.0 - beta2) * (g * g)\n", + " mh = m_a[k] / (1.0 - beta1 ** t)\n", + " vh = v_a[k] / (1.0 - beta2 ** t)\n", + " p -= lr * mh / (np.sqrt(vh) + eps)\n", + " return net\n", + "\n", + "\n", + "# --- Gates instrument : retrouver les valeurs de reference AVANT toute mesure (pattern L934) ---\n", + "def _ref_tables(n):\n", + " B = np.array(list(itertools.product([0, 1], repeat=n)))\n", + " s1 = B.sum(axis=1)\n", + " return {\n", + " f'dictateur_{n}': (B[:, 0] == 1).astype(int),\n", + " f'OR_{n}': (s1 >= 1).astype(int),\n", + " f'AND_{n}': (s1 == n).astype(int),\n", + " f'MAJ_{n}': (s1 >= n // 2 + 1).astype(int),\n", + " f'PAR_{n}': (s1 % 2).astype(int),\n", + " }\n", + "\n", + "\n", + "# valeurs classiques, verifiees a la main. MAJ_3 : s_max = 2 (les points de poids 1 et 2\n", + "# ont exactement 2 voisins qui basculent la majorite ; 2 >= sqrt(3), Huang tient). Une\n", + "# premiere version de ce gate attendait s_max = 3 par erreur de calcul manuel -- c'est le\n", + "# gate qui a raison, correction apportee AVANT toute mesure du banc.\n", + "gates_attendus = {\n", + " 'dictateur_4': (1, 1),\n", + " 'OR_4': (4, 4),\n", + " 'MAJ_3': (3, 2),\n", + " 'PAR_4': (4, 4),\n", + "}\n", + "print('=== Gates instrument (valeurs de reference) ===')\n", + "all_gates_ok = True\n", + "for name, (deg_att, smax_att) in gates_attendus.items():\n", + " n_g = int(name[-1])\n", + " tab = 1.0 - 2.0 * _ref_tables(n_g)[name].astype(float)\n", + " d_g = fourier_degree(tab, n_g)\n", + " s_g = int(hypercube_sensitivity(tab, n_g).max())\n", + " ok = (d_g == deg_att) and (s_g == smax_att)\n", + " all_gates_ok &= ok\n", + " print(f' {name:>12} : deg={d_g} (attendu {deg_att}), s_max={s_g} (attendu {smax_att}) -> {\"OK\" if ok else \"ECHEC\"}')\n", + "\n", + "# Verification EXHAUSTIVE du theoreme de Huang sur n=3 : les 256 fonctions booleennes\n", + "n_viol_huang = 0\n", + "for v in range(2 ** (2 ** 3)):\n", + " tab = 1.0 - 2.0 * np.array([(v >> i) & 1 for i in range(8)], dtype=float)\n", + " if np.all(tab == tab[0]):\n", + " continue # constante : deg = 0, inegalite triviale\n", + " d_h = fourier_degree(tab, 3)\n", + " s_h = int(hypercube_sensitivity(tab, 3).max())\n", + " if s_h < np.sqrt(d_h) - 1e-12:\n", + " n_viol_huang += 1\n", + "print(f' theoreme Huang verifie exhaustivement sur les 256 fonctions de n=3 : {n_viol_huang} violation(s)')\n", + "all_gates_ok &= (n_viol_huang == 0)\n", + "\n", + "# Gate optimiseur (amendements v2/v3) : le gradient corrige + ponderation equilibree\n", + "# doivent laisser le regime NORMAL fitter PARFAITEMENT une cible complete (graine de\n", + "# calibration 11, disjointe des graines de test), et le regime ADVERSARIAL lam=0.5\n", + "# doit rester apprenable sur une cible equilibree (train_acc >= 0.9).\n", + "_tab_maj3 = 1.0 - 2.0 * _ref_tables(3)['MAJ_3'].astype(float)\n", + "_X3 = hypercube_inputs(3)\n", + "_net_g = train_mlp(3, _X3, _tab_maj3, regime='normal', seed=2011, steps=600, lr=0.1)\n", + "_acc_norm = float(np.mean(np.where(_net_g.forward(_X3) >= 0, 1.0, -1.0) == _tab_maj3))\n", + "_net_a = train_mlp(3, _X3, _tab_maj3, regime='adversarial', seed=2011, steps=600, lr=0.1)\n", + "_acc_adv = float(np.mean(np.where(_net_a.forward(_X3) >= 0, 1.0, -1.0) == _tab_maj3))\n", + "print(f' gate optimiseur (MAJ_3 complet, graine 11 disjointe) : train_acc normal = {_acc_norm:.3f}, '\n", + " f'adversarial (lam=0.5) = {_acc_adv:.3f} (attendus 1.0 / >= 0.9)')\n", + "all_gates_ok &= (_acc_norm == 1.0) and (_acc_adv >= 0.9)\n", + "\n", + "if not all_gates_ok:\n", + " raise RuntimeError('Gates instrument en echec : ne pas mesurer avant correction.')\n", + "print('Gates instrument PASS : l instrument discrimine (deg et s_max exacts), respecte Huang '\n", + " 'sur n=3, et l optimiseur converge (normal ET adversarial lam=0.5).')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0a857aa1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-26T23:44:39.682319Z", + "iopub.status.busy": "2026-09-26T23:44:39.682065Z", + "iopub.status.idle": "2026-09-26T23:44:42.508027Z", + "shell.execute_reply": "2026-09-26T23:44:42.506924Z" + }, + "id": "ict-15g-learned-bench", + "papermill": { + "duration": 2.831117, + "end_time": "2026-09-26T23:44:42.509250+00:00", + "exception": false, + "start_time": "2026-09-26T23:44:39.678133+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- n=3 : cible TRI_bal_3 (labels controles) = [1, 0, 1, 0, 0, 1, 0, 1]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--- n=4 : cible TRI_bal_4 (labels controles) = [0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "=== Banc #17977 : mesure complete (cible = reference ; plafond = s_max == n) ===\n", + " n cible graine regime train_acc deg s_max ratio degenerate plafond\n", + " 3 OR_3 -1 cible 1.000000 3 3 1.732051 False True\n", + " 3 OR_3 0 untrained 0.500000 3 2 1.154701 False False\n", + " 3 OR_3 0 normal 1.000000 3 3 1.732051 False True\n", + " 3 OR_3 0 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 OR_3 1 untrained 0.500000 1 1 1.000000 False False\n", + " 3 OR_3 1 normal 1.000000 0 0 NaN True False\n", + " 3 OR_3 1 adversarial 1.000000 0 0 NaN True False\n", + " 3 OR_3 7 untrained 0.500000 3 2 1.154701 False False\n", + " 3 OR_3 7 normal 1.000000 2 2 1.414214 False False\n", + " 3 OR_3 7 adversarial 1.000000 0 0 NaN True False\n", + " 3 OR_3 42 untrained 0.500000 3 2 1.154701 False False\n", + " 3 OR_3 42 normal 1.000000 3 2 1.154701 False False\n", + " 3 OR_3 42 adversarial 0.833333 3 2 1.154701 True False\n", + " 3 AND_3 -1 cible 1.000000 3 3 1.732051 False True\n", + " 3 AND_3 0 untrained 0.666667 3 2 1.154701 False False\n", + " 3 AND_3 0 normal 1.000000 2 2 1.414214 False False\n", + " 3 AND_3 0 adversarial 1.000000 0 0 NaN True False\n", + " 3 AND_3 1 untrained 0.666667 1 1 1.000000 False False\n", + " 3 AND_3 1 normal 1.000000 2 2 1.414214 False False\n", + " 3 AND_3 1 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 AND_3 7 untrained 0.666667 3 2 1.154701 False False\n", + " 3 AND_3 7 normal 1.000000 3 3 1.732051 False True\n", + " 3 AND_3 7 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 AND_3 42 untrained 0.166667 3 2 1.154701 False False\n", + " 3 AND_3 42 normal 1.000000 3 3 1.732051 False True\n", + " 3 AND_3 42 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 MAJ_3 -1 cible 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 0 untrained 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 0 normal 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 0 adversarial 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 1 untrained 0.666667 1 1 1.000000 False False\n", + " 3 MAJ_3 1 normal 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 1 adversarial 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 7 untrained 0.500000 3 2 1.154701 False False\n", + " 3 MAJ_3 7 normal 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 7 adversarial 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 42 untrained 0.333333 3 2 1.154701 False False\n", + " 3 MAJ_3 42 normal 1.000000 3 2 1.154701 False False\n", + " 3 MAJ_3 42 adversarial 1.000000 3 2 1.154701 False False\n", + " 3 PAR_3 -1 cible 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 0 untrained 0.166667 3 2 1.154701 False False\n", + " 3 PAR_3 0 normal 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 0 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 1 untrained 0.500000 1 1 1.000000 False False\n", + " 3 PAR_3 1 normal 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 1 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 7 untrained 0.666667 3 2 1.154701 False False\n", + " 3 PAR_3 7 normal 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 7 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 42 untrained 0.333333 3 2 1.154701 False False\n", + " 3 PAR_3 42 normal 1.000000 3 3 1.732051 False True\n", + " 3 PAR_3 42 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 TRI_bal_3 -1 cible 1.000000 2 2 1.414214 False False\n", + " 3 TRI_bal_3 0 untrained 0.333333 3 2 1.154701 False False\n", + " 3 TRI_bal_3 0 normal 1.000000 2 2 1.414214 False False\n", + " 3 TRI_bal_3 0 adversarial 1.000000 2 2 1.414214 False False\n", + " 3 TRI_bal_3 1 untrained 0.500000 1 1 1.000000 False False\n", + " 3 TRI_bal_3 1 normal 1.000000 3 3 1.732051 False True\n", + " 3 TRI_bal_3 1 adversarial 1.000000 3 3 1.732051 False True\n", + " 3 TRI_bal_3 7 untrained 0.666667 3 2 1.154701 False False\n", + " 3 TRI_bal_3 7 normal 1.000000 2 2 1.414214 False False\n", + " 3 TRI_bal_3 7 adversarial 1.000000 2 2 1.414214 False False\n", + " 3 TRI_bal_3 42 untrained 0.500000 3 2 1.154701 False False\n", + " 3 TRI_bal_3 42 normal 1.000000 2 2 1.414214 False False\n", + " 3 TRI_bal_3 42 adversarial 0.833333 3 3 1.732051 True True\n", + " 4 OR_4 -1 cible 1.000000 4 4 2.000000 False True\n", + " 4 OR_4 0 untrained 0.500000 3 3 1.732051 False False\n", + " 4 OR_4 0 normal 1.000000 3 3 1.732051 False False\n", + " 4 OR_4 0 adversarial 1.000000 3 3 1.732051 False False\n", + " 4 OR_4 1 untrained 0.500000 1 1 1.000000 False False\n", + " 4 OR_4 1 normal 1.000000 0 0 NaN True False\n", + " 4 OR_4 1 adversarial 1.000000 0 0 NaN True False\n", + " 4 OR_4 7 untrained 0.583333 1 1 1.000000 False False\n", + " 4 OR_4 7 normal 1.000000 0 0 NaN True False\n", + " 4 OR_4 7 adversarial 1.000000 0 0 NaN True False\n", + " 4 OR_4 42 untrained 0.666667 3 2 1.154701 False False\n", + " 4 OR_4 42 normal 1.000000 3 3 1.732051 False False\n", + " 4 OR_4 42 adversarial 0.750000 4 3 1.500000 True False\n", + " 4 AND_4 -1 cible 1.000000 4 4 2.000000 False True\n", + " 4 AND_4 0 untrained 0.583333 3 3 1.732051 False False\n", + " 4 AND_4 0 normal 1.000000 0 0 NaN True False\n", + " 4 AND_4 0 adversarial 1.000000 0 0 NaN True False\n", + " 4 AND_4 1 untrained 0.416667 1 1 1.000000 False False\n", + " 4 AND_4 1 normal 1.000000 3 3 1.732051 False False\n", + " 4 AND_4 1 adversarial 0.833333 4 3 1.500000 True False\n", + " 4 AND_4 7 untrained 0.416667 1 1 1.000000 False False\n", + " 4 AND_4 7 normal 1.000000 0 0 NaN True False\n", + " 4 AND_4 7 adversarial 1.000000 0 0 NaN True False\n", + " 4 AND_4 42 untrained 0.500000 3 2 1.154701 False False\n", + " 4 AND_4 42 normal 1.000000 3 3 1.732051 False False\n", + " 4 AND_4 42 adversarial 0.833333 4 3 1.500000 True False\n", + " 4 MAJ_4 -1 cible 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 0 untrained 0.833333 3 3 1.732051 False False\n", + " 4 MAJ_4 0 normal 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 0 adversarial 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 1 untrained 0.333333 1 1 1.000000 False False\n", + " 4 MAJ_4 1 normal 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 1 adversarial 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 7 untrained 0.250000 1 1 1.000000 False False\n", + " 4 MAJ_4 7 normal 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 7 adversarial 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 42 untrained 0.416667 3 2 1.154701 False False\n", + " 4 MAJ_4 42 normal 1.000000 4 3 1.500000 False False\n", + " 4 MAJ_4 42 adversarial 1.000000 4 3 1.500000 False False\n", + " 4 PAR_4 -1 cible 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 0 untrained 0.583333 3 3 1.732051 False False\n", + " 4 PAR_4 0 normal 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 0 adversarial 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 1 untrained 0.666667 1 1 1.000000 False False\n", + " 4 PAR_4 1 normal 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 1 adversarial 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 7 untrained 0.500000 1 1 1.000000 False False\n", + " 4 PAR_4 7 normal 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 7 adversarial 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 42 untrained 0.416667 3 2 1.154701 False False\n", + " 4 PAR_4 42 normal 1.000000 4 4 2.000000 False True\n", + " 4 PAR_4 42 adversarial 1.000000 4 4 2.000000 False True\n", + " 4 TRI_bal_4 -1 cible 1.000000 4 4 2.000000 False True\n", + " 4 TRI_bal_4 0 untrained 0.500000 3 3 1.732051 False False\n", + " 4 TRI_bal_4 0 normal 1.000000 4 4 2.000000 False True\n", + " 4 TRI_bal_4 0 adversarial 1.000000 4 4 2.000000 False True\n", + " 4 TRI_bal_4 1 untrained 0.583333 1 1 1.000000 False False\n", + " 4 TRI_bal_4 1 normal 1.000000 4 3 1.500000 False False\n", + " 4 TRI_bal_4 1 adversarial 1.000000 4 3 1.500000 False False\n", + " 4 TRI_bal_4 7 untrained 0.583333 1 1 1.000000 False False\n", + " 4 TRI_bal_4 7 normal 1.000000 4 4 2.000000 False True\n", + " 4 TRI_bal_4 7 adversarial 1.000000 3 4 2.309401 False True\n", + " 4 TRI_bal_4 42 untrained 0.583333 3 2 1.154701 False False\n", + " 4 TRI_bal_4 42 normal 1.000000 4 3 1.500000 False False\n", + " 4 TRI_bal_4 42 adversarial 1.000000 4 3 1.500000 False False\n", + "\n", + "Entrees entrainees : 80 ; degeneres (garde pre-enregistree) : 17 (21.2%)\n" + ] + } + ], + "source": [ + "# --- Banc #17977 : execution (design gele, cf pre-enregistrement ci-dessus) ---\n", + "SEEDS_BENCH = [0, 1, 7, 42]\n", + "STEPS_BENCH, LR_BENCH, HIDDEN_BENCH = 400, 0.05, 16 # budget FIXE pre-enregistre\n", + "\n", + "rows_17977 = []\n", + "for n_b in (3, 4):\n", + " B = np.array(list(itertools.product([0, 1], repeat=n_b)))\n", + " X_full = 1.0 - 2.0 * B.astype(float) # (2^n, n)\n", + " # Cibles : labels CONTROLES en encodage b (0/1), convertis en +-1 pour l entrainement\n", + " targets_b = {\n", + " f'OR_{n_b}': (B.sum(axis=1) >= 1).astype(int),\n", + " f'AND_{n_b}': (B.sum(axis=1) == n_b).astype(int),\n", + " f'MAJ_{n_b}': (B.sum(axis=1) >= n_b // 2 + 1).astype(int),\n", + " f'PAR_{n_b}': (B.sum(axis=1) % 2).astype(int),\n", + " }\n", + " r_tgt = np.random.default_rng(20260720 + n_b) # TRI_bal : table aleatoire equilibree, graine publiee\n", + " tab_bal = np.array([1] * (2 ** (n_b - 1)) + [0] * (2 ** (n_b - 1)))\n", + " r_tgt.shuffle(tab_bal)\n", + " targets_b[f'TRI_bal_{n_b}'] = tab_bal\n", + " print(f'--- n={n_b} : cible TRI_bal_{n_b} (labels controles) = {tab_bal.tolist()}')\n", + "\n", + " for tname, t01 in targets_b.items():\n", + " y_full = 1.0 - 2.0 * t01.astype(float) # +-1\n", + " deg_t = fourier_degree(y_full, n_b)\n", + " s_t = int(hypercube_sensitivity(y_full, n_b).max())\n", + " rows_17977.append(dict(n=n_b, cible=tname, graine=-1, regime='cible',\n", + " train_acc=1.0, deg=deg_t, s_max=s_t,\n", + " ratio=(s_t / np.sqrt(deg_t)) if deg_t > 0 else float('nan'),\n", + " degenerate=False, plafond=(s_t == n_b)))\n", + " for seed in SEEDS_BENCH:\n", + " # Split deterministe par graine, IDENTIQUE entre regimes (paires appariees)\n", + " r_split = np.random.default_rng(1000 + seed)\n", + " perm = r_split.permutation(len(X_full))\n", + " n_train = 3 * len(X_full) // 4 # 75 % train / 25 % holdout\n", + " itr = perm[:n_train]\n", + " Xtr, ytr = X_full[itr], y_full[itr]\n", + " for regime in ('untrained', 'normal', 'adversarial'):\n", + " net = train_mlp(n_b, Xtr, ytr, regime=regime, seed=2000 + seed,\n", + " steps=STEPS_BENCH, lr=LR_BENCH)\n", + " table = np.where(net.forward(X_full) >= 0, 1.0, -1.0)\n", + " constant = bool(np.all(table == table[0]))\n", + " train_acc = float(np.mean(np.where(net.forward(Xtr) >= 0, 1.0, -1.0) == ytr))\n", + " deg_r = fourier_degree(table, n_b)\n", + " s_r = int(hypercube_sensitivity(table, n_b).max())\n", + " # Garde pre-enregistree : regimes entraines -> constance OU train_acc < 0.9 ;\n", + " # controle untrained -> seule la constance exclut.\n", + " if regime == 'untrained':\n", + " degen = constant\n", + " else:\n", + " degen = constant or (train_acc < 0.9)\n", + " rows_17977.append(dict(n=n_b, cible=tname, graine=seed, regime=regime,\n", + " train_acc=train_acc, deg=deg_r, s_max=s_r,\n", + " ratio=(s_r / np.sqrt(deg_r)) if (deg_r > 0 and not constant) else float('nan'),\n", + " degenerate=degen, plafond=(s_r == n_b)))\n", + "\n", + "bench_17977 = pd.DataFrame(rows_17977)\n", + "print('\\n=== Banc #17977 : mesure complete (cible = reference ; plafond = s_max == n) ===')\n", + "print(bench_17977.to_string(index=False))\n", + "\n", + "n_trained = len(bench_17977[bench_17977['regime'].isin(['normal', 'adversarial'])])\n", + "n_degen = int(bench_17977.loc[bench_17977['regime'].isin(['normal', 'adversarial']), 'degenerate'].sum())\n", + "print(f'\\nEntrees entrainees : {n_trained} ; degeneres (garde pre-enregistree) : {n_degen} '\n", + " f'({n_degen / n_trained:.1%})')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6fbaf32e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-26T23:44:42.517640Z", + "iopub.status.busy": "2026-09-26T23:44:42.517353Z", + "iopub.status.idle": "2026-09-26T23:44:43.370440Z", + "shell.execute_reply": "2026-09-26T23:44:43.369328Z" + }, + "id": "ict-15g-learned-verdicts", + "papermill": { + "duration": 0.857725, + "end_time": "2026-09-26T23:44:43.371349+00:00", + "exception": false, + "start_time": "2026-09-26T23:44:42.513624+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Bande aleatoire appariee en degre (controle 2) : ratio par degre ===\n", + " n med p5 p95 vmin vmax\n", + "n deg \n", + "3 1 6 1.000000 1.000000 1.000000 1.000000 1.000000\n", + " 2 62 1.414214 1.414214 2.121320 1.414214 2.121320\n", + " 3 186 1.732051 1.154701 1.732051 1.154701 1.732051\n", + "4 2 9 1.414214 1.414214 1.414214 1.414214 1.414214\n", + " 3 564 1.732051 1.732051 2.309401 1.154701 2.309401\n", + " 4 2427 2.000000 1.500000 2.000000 1.000000 2.000000\n", + "\n", + "=== Carte de saturation du ratio s_max / sqrt(deg) (tables realisees non-degeneres) ===\n", + " n_entrees ratio_med ratio_min ratio_max frac_plafond\n", + "n regime deg \n", + "3 adversarial 2 2 1.414214 1.414214 1.414214 0.000000\n", + " 3 13 1.732051 1.154701 1.732051 0.692308\n", + " normal 2 6 1.414214 1.414214 1.414214 0.000000\n", + " 3 13 1.732051 1.154701 1.732051 0.615385\n", + " untrained 1 5 1.000000 1.000000 1.000000 0.000000\n", + " 3 15 1.154701 1.154701 1.154701 0.000000\n", + "4 adversarial 3 2 2.020726 1.732051 2.309401 0.500000\n", + " 4 11 1.500000 1.500000 2.000000 0.454545\n", + " normal 3 4 1.732051 1.732051 1.732051 0.000000\n", + " 4 12 1.750000 1.500000 2.000000 0.500000\n", + " untrained 1 10 1.000000 1.000000 1.000000 0.000000\n", + " 3 10 1.443376 1.154701 1.732051 0.000000\n", + "\n", + "Fraction des tables entrainees dont le ratio tombe dans la bande aleatoire appariee en degre [p5, p95] :\n", + "regime\n", + "adversarial 1.0\n", + "normal 1.0\n", + "\n", + "--- P1 (declaree) : l adversarial reduit s_max ---\n", + " paires non-degeneres : 28 ; Delta = s_max_adv - s_max_norm : 0 negatives / 27 nulles / 1 positives ; mediane = 0.0\n", + " P1 : NON CONFIRMEE\n", + "--- P2 (theoreme, gate instrument) : 118 tables non constantes, 0 violation(s) de s_max >= sqrt(deg) -> OK\n", + "--- P3 (controle untrained) : mediane s_max normal = 3.0, untrained = 2.0 -> NON CONFIRMEE (l entrainement n augmente pas la sensibilite)\n", + "--- P4 (resolution/saturation) : degres distincts = 3 (>= 2 requis), fraction au plafond s_max = n : 46.0% (< 75 % requis) -> CONFIRMEE\n", + "\n", + "=== VERDICT #17977 (criteres declares) : FAIL ===\n", + " fraction degeneree (entraines) : 21.2% (seuil INCONCLUSIVE : > 33,3 %)\n", + " Rappel des conditions : INVALID si P2 violee ; INCONCLUSIVE si P4 non tenue, n_paires < 8 ou degen > 1/3 ; PASS si P1 confirmee ; FAIL sinon (mediane >= 0).\n", + "\n", + "--- Complement post-revue : Delta ratio = ratio_adv - ratio_norm (memes paires qualifiees que P1) ---\n", + " paires : 28 ; negatives = 0, nulles = 26, positives = 2 ; mediane = 0.000000\n", + " paire (4, 'TRI_bal_4', 7) : Delta ratio = +0.309401 (s_max 4->4, deg 4->3)\n", + " paire (3, 'AND_3', 1) : Delta ratio = +0.317837 (s_max 2->3, deg 2->3)\n", + " Lecture descriptive (hors verdict declare) : aucun deplacement negatif du ratio ;\n", + " les paires a s_max constant montrent que degre realise et ratio peuvent bouger seuls.\n" + ] + } + ], + "source": [ + "# --- Banc #17977 : carte de saturation + verdicts du plan DECLARE ---\n", + "# (analyse executee selon les seuils declares ci-dessus ; aucun re-seuillage post-hoc)\n", + "\n", + "# Bande aleatoire appariee en degre : n=3 exhaustive (256 fonctions), n=4 echantillon 3000\n", + "rand_rows = []\n", + "for n_r in (3, 4):\n", + " if n_r == 3:\n", + " tables_rand = [1.0 - 2.0 * np.array([(v >> i) & 1 for i in range(8)], dtype=float)\n", + " for v in range(2 ** 8)]\n", + " else:\n", + " r_rand = np.random.default_rng(20260721)\n", + " tables_rand = [1.0 - 2.0 * r_rand.integers(0, 2, 16).astype(float) for _ in range(3000)]\n", + " for tab_r in tables_rand:\n", + " if np.all(tab_r == tab_r[0]):\n", + " continue\n", + " d_r = fourier_degree(tab_r, n_r)\n", + " s_r = int(hypercube_sensitivity(tab_r, n_r).max())\n", + " rand_rows.append(dict(n=n_r, deg=d_r, ratio=s_r / np.sqrt(d_r)))\n", + "rand_band = pd.DataFrame(rand_rows)\n", + "print('=== Bande aleatoire appariee en degre (controle 2) : ratio par degre ===')\n", + "band_summary = rand_band.groupby(['n', 'deg'])['ratio'].agg(\n", + " n='size', med='median', p5=lambda s: float(np.percentile(s, 5)),\n", + " p95=lambda s: float(np.percentile(s, 95)), vmin='min', vmax='max')\n", + "print(band_summary.to_string())\n", + "\n", + "# Entrees conclusives : non-degeneres, ratio defini, hors lignes 'cible' (reference)\n", + "conc_17977 = bench_17977[(~bench_17977['degenerate'])\n", + " & bench_17977['ratio'].notna()\n", + " & (bench_17977['regime'] != 'cible')]\n", + "\n", + "# Carte de saturation : stats du ratio par (n, regime, deg) pour les tables realisees\n", + "print('\\n=== Carte de saturation du ratio s_max / sqrt(deg) (tables realisees non-degeneres) ===')\n", + "map_17977 = conc_17977.groupby(['n', 'regime', 'deg']).agg(\n", + " n_entrees=('ratio', 'size'),\n", + " ratio_med=('ratio', 'median'),\n", + " ratio_min=('ratio', 'min'),\n", + " ratio_max=('ratio', 'max'),\n", + " frac_plafond=('plafond', 'mean'))\n", + "print(map_17977.to_string())\n", + "\n", + "# Position vs bande aleatoire appariee en degre (descriptif) : ratio dans [p5, p95] de sa bande ?\n", + "def _dans_bande(row):\n", + " grp = rand_band[(rand_band['n'] == row['n']) & (rand_band['deg'] == row['deg'])]\n", + " if grp.empty:\n", + " return np.nan\n", + " lo, hi = np.percentile(grp['ratio'], [5, 95])\n", + " return bool(lo - 1e-12 <= row['ratio'] <= hi + 1e-12)\n", + "\n", + "trained_mask = conc_17977['regime'].isin(['normal', 'adversarial'])\n", + "conc_trained = conc_17977[trained_mask].copy()\n", + "conc_trained['dans_bande'] = conc_trained.apply(_dans_bande, axis=1)\n", + "in_band = conc_trained.groupby('regime')['dans_bande'].mean()\n", + "print('\\nFraction des tables entrainees dont le ratio tombe dans la bande aleatoire '\n", + " 'appariee en degre [p5, p95] :')\n", + "print(in_band.to_string())\n", + "\n", + "# --- P1 : deplacement adversarial (paires appariees n x cible x graine) ---\n", + "pn = conc_17977[conc_17977['regime'] == 'normal'].set_index(['n', 'cible', 'graine'])['s_max']\n", + "pa = conc_17977[conc_17977['regime'] == 'adversarial'].set_index(['n', 'cible', 'graine'])['s_max']\n", + "common = pn.index.intersection(pa.index)\n", + "delta_smax = (pa.loc[common] - pn.loc[common]).sort_values()\n", + "n_pairs = len(delta_smax)\n", + "med_delta = float(delta_smax.median()) if n_pairs else float('nan')\n", + "n_neg, n_pos = int((delta_smax < 0).sum()), int((delta_smax > 0).sum())\n", + "p1_confirmee = bool(n_pairs >= 8 and med_delta < 0 and n_neg > n_pos)\n", + "print(f'\\n--- P1 (declaree) : l adversarial reduit s_max ---')\n", + "print(f' paires non-degeneres : {n_pairs} ; Delta = s_max_adv - s_max_norm : '\n", + " f'{n_neg} negatives / {n_pairs - n_neg - n_pos} nulles / {n_pos} positives ; mediane = {med_delta}')\n", + "print(f' P1 : {\"CONFIRMEE\" if p1_confirmee else \"NON CONFIRMEE\"}')\n", + "\n", + "# --- P2 : gate theoreme sur TOUTES les tables realisees non constantes ---\n", + "non_const = bench_17977[(bench_17977['deg'] > 0) & (bench_17977['s_max'] >= 0)]\n", + "p2_ok = bool((non_const['s_max'] >= np.sqrt(non_const['deg']) - 1e-12).all())\n", + "n_p2_viol = int((non_const['s_max'] < np.sqrt(non_const['deg']) - 1e-12).sum())\n", + "print(f'--- P2 (theoreme, gate instrument) : {len(non_const)} tables non constantes, '\n", + " f'{n_p2_viol} violation(s) de s_max >= sqrt(deg) -> {\"OK\" if p2_ok else \"INVALID\"}')\n", + "\n", + "# --- P3 : controle untrained ---\n", + "med_norm = float(conc_17977[conc_17977['regime'] == 'normal']['s_max'].median())\n", + "med_untr = float(conc_17977[conc_17977['regime'] == 'untrained']['s_max'].median())\n", + "p3_confirmee = bool(med_norm <= med_untr)\n", + "print(f'--- P3 (controle untrained) : mediane s_max normal = {med_norm}, untrained = {med_untr} '\n", + " f'-> {\"CONFIRMEE\" if p3_confirmee else \"NON CONFIRMEE\"} (l entrainement n augmente pas la sensibilite)')\n", + "\n", + "# --- P4 : resolution + non-saturation (reserves #9771/#9764) ---\n", + "deg_distincts = conc_trained['deg'].nunique()\n", + "frac_plafond_glob = float(conc_trained['plafond'].mean())\n", + "p4_ok = bool(deg_distincts >= 2 and frac_plafond_glob < 0.75)\n", + "print(f'--- P4 (resolution/saturation) : degres distincts = {deg_distincts} (>= 2 requis), '\n", + " f'fraction au plafond s_max = n : {frac_plafond_glob:.1%} (< 75 % requis) '\n", + " f'-> {\"CONFIRMEE\" if p4_ok else \"NON CONFIRMEE\"}')\n", + "\n", + "# --- Verdict global (criteres declares) ---\n", + "frac_degen = n_degen / n_trained\n", + "if not p2_ok:\n", + " verdict_17977 = 'INVALID'\n", + "elif (not p4_ok) or n_pairs < 8 or frac_degen > 1 / 3:\n", + " verdict_17977 = 'INCONCLUSIVE'\n", + "elif p1_confirmee:\n", + " verdict_17977 = 'PASS'\n", + "else:\n", + " verdict_17977 = 'FAIL'\n", + "print(f'\\n=== VERDICT #17977 (criteres declares) : {verdict_17977} ===')\n", + "print(f' fraction degeneree (entraines) : {frac_degen:.1%} (seuil INCONCLUSIVE : > 33,3 %)')\n", + "print(' Rappel des conditions : INVALID si P2 violee ; INCONCLUSIVE si P4 non tenue, '\n", + " 'n_paires < 8 ou degen > 1/3 ; PASS si P1 confirmee ; FAIL sinon (mediane >= 0).')\n", + "\n", + "# --- Complement post-revue : Delta RATIO apparie (la quantite de l'issue #17977) ---\n", + "# P1 declaree porte sur Delta s_max, PROXY de la quantite de l'issue (le ratio).\n", + "# Ce bloc, ajoute apres revue, mesure la quantite elle-meme : lecture DESCRIPTIVE,\n", + "# sans critere declare -- aucun seuil du plan ne porte sur Delta ratio. Rappel :\n", + "# Delta s_max = 0 n'entraine PAS Delta ratio = 0 (le degre de la table realisee\n", + "# peut differer entre les deux regimes d'une meme paire).\n", + "rn_q = conc_17977[conc_17977['regime'] == 'normal'].set_index(['n', 'cible', 'graine'])\n", + "ra_q = conc_17977[conc_17977['regime'] == 'adversarial'].set_index(['n', 'cible', 'graine'])\n", + "common_q = rn_q.index.intersection(ra_q.index)\n", + "delta_ratio = (ra_q.loc[common_q, 'ratio'] - rn_q.loc[common_q, 'ratio']).sort_values()\n", + "n_neg_r, n_nul_r, n_pos_r = int((delta_ratio < 0).sum()), int((delta_ratio == 0).sum()), int((delta_ratio > 0).sum())\n", + "print('\\n--- Complement post-revue : Delta ratio = ratio_adv - ratio_norm (memes paires qualifiees que P1) ---')\n", + "print(' paires : %d ; negatives = %d, nulles = %d, positives = %d ; mediane = %.6f'\n", + " % (len(delta_ratio), n_neg_r, n_nul_r, n_pos_r, float(delta_ratio.median())))\n", + "for k_q, v_q in delta_ratio[delta_ratio != 0].items():\n", + " s_n = int(rn_q.loc[k_q, 's_max']); s_a = int(ra_q.loc[k_q, 's_max'])\n", + " d_n = int(rn_q.loc[k_q, 'deg']); d_a = int(ra_q.loc[k_q, 'deg'])\n", + " print(' paire %s : Delta ratio = %+.6f (s_max %d->%d, deg %d->%d)'\n", + " % (k_q, float(v_q), s_n, s_a, d_n, d_a))\n", + "print(' Lecture descriptive (hors verdict declare) : aucun deplacement negatif du ratio ;')\n", + "print(' les paires a s_max constant montrent que degre realise et ratio peuvent bouger seuls.')" + ] + }, + { + "cell_type": "markdown", + "id": "acea4183", + "metadata": { + "id": "ict-15g-learned-interp", + "papermill": { + "duration": 0.002588, + "end_time": "2026-09-26T23:44:43.377273+00:00", + "exception": false, + "start_time": "2026-09-26T23:44:43.374685+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "### Interprétation du banc #17977 — FAIL du critère déclaré sur Δs_max\n", + "\n", + "**Deux quantités à distinguer.** Le verdict `FAIL` porte sur P1, le critère d'analyse déclaré dans ce notebook : « l'entraînement adversarial réduit s_max ». Sur **28 paires appariées non dégénérées**, 27 ont Δs_max = 0, une a Δs_max > 0, aucune n'a Δs_max < 0 ; la médiane est 0. Le commentaire de claim sur l'issue formulait toutefois l'hypothèse en termes de **ratio** s_max/√deg : P1 (sur Δs_max) n'en est qu'un proxy et ne suffit pas, seul, à répondre à cette formulation. Le complément calculé après revue mesure donc directement Δratio : **26 valeurs nulles, 2 positives, aucune négative**, médiane 0. Sur (n=4, TRI_bal_4, graine 7), s_max reste 4 mais deg passe de 4 à 3 (Δratio = +0,309) ; sur (n=3, AND_3, graine 1), s_max passe de 2 à 3 et deg de 2 à 3 (Δratio = +0,318). Ces résultats ne montrent **aucune baisse dans les paires qualifiées**, mais ne constituent pas un test pré-enregistré du ratio. Δs_max nul n'implique ni même table ni même ratio.\n", + "\n", + "**Borne et portée de l'effet.** P2 donne 0 violation sur les 118 tables réalisées non constantes ; le contrôle exhaustif n=3 donne également 0 violation sur 256 fonctions. La borne de Huang sur l'hypercube vrai n'est pas réfutée. λ = 1 a été écarté à la calibration (graines disjointes {11, 22}) : à ce poids le terme adversarial bloque l'apprentissage, et le taux de dégénérés mesuré alors n'est pas conservé dans une sortie committée ; à λ = 0,5, les paires qualifiées ne montrent aucune baisse de s_max ou du ratio. Seuls λ ∈ {0 ; 0,5 ; 1} ont été examinés ; les valeurs intermédiaires ne sont pas exclues. Le banc ne mesure ni la géométrie des logits ni les grands n : il ne peut expliquer mécaniquement l'absence de baisse ni la généraliser.\n", + "\n", + "**Carte de saturation.** Les ratios mesurés des tables non constantes sont dans [1,0 ; 2,31]. Parmi les entrées entraînées non dégénérées, 46,0 % atteignent le plafond s_max = n ; trois degrés distincts sont représentés, ce qui satisfait le contrôle P4 déclaré. La bande de fonctions aléatoires appariées en degré recouvre 100 % des ratios appris aux quantiles [p5, p95] de l'échantillon retenu. C'est un **chevauchement descriptif**, pas une preuve d'indistinguabilité statistique.\n", + "\n", + "| Observation | Valeur mesurée | Lecture |\n", + "|---|---|---|\n", + "| ratio médian (n=4, deg=4, normal) | 1,75 | sous la médiane aléatoire appariée (2,0) |\n", + "| ratio médian (n=4, deg=4, adversarial) | 1,50 | également dans la bande témoin |\n", + "| médiane s_max : normal vs non entraîné | 3,0 vs 2,0 | P3 non confirmée ; la comparaison brute ne contrôle pas la distribution des degrés réalisés |\n", + "| dégénérés parmi les régimes entraînés | 17/80 = 21,2 % | exclus des paires qualifiées, mais comptés dans le garde |\n", + "\n", + "Les cas dégénérés sont surtout des cibles OR_4/AND_4 dont le point minoritaire tombe dans le holdout : le train ne contient alors qu'une classe, et une prédiction constante peut atteindre train_acc = 1 sans résoudre la cible complète. Le résultat reste limité aux hypercubes de 3 et 4 bits, au MLP de 16 neurones cachés, au split 75/25, à 400 étapes, aux quatre graines et aux cinq cibles étudiées. La **transposition conjecturale** du banc hérité, qui emploie un graphe de transitions observées plutôt que l'hypercube, conserve son statut distinct." ] }, { @@ -581,10 +1400,10 @@ "id": "ict-15b-empirical-exo1-md", "metadata": { "papermill": { - "duration": 0.002496, - "end_time": "2026-09-01T06:42:02.326574+00:00", + "duration": 0.003, + "end_time": "2026-09-26T23:44:43.383003+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.324078+00:00", + "start_time": "2026-09-26T23:44:43.380003+00:00", "status": "completed" }, "tags": [] @@ -601,20 +1420,20 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "id": "ict-15b-empirical-exo1-stub", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:02.332983Z", - "iopub.status.busy": "2026-09-01T06:42:02.331984Z", - "iopub.status.idle": "2026-09-01T06:42:02.345595Z", - "shell.execute_reply": "2026-09-01T06:42:02.345092Z" + "iopub.execute_input": "2026-09-26T23:44:43.391338Z", + "iopub.status.busy": "2026-09-26T23:44:43.391015Z", + "iopub.status.idle": "2026-09-26T23:44:43.397484Z", + "shell.execute_reply": "2026-09-26T23:44:43.396656Z" }, "papermill": { - "duration": 0.018198, - "end_time": "2026-09-01T06:42:02.347367+00:00", + "duration": 0.011991, + "end_time": "2026-09-26T23:44:43.398148+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.329169+00:00", + "start_time": "2026-09-26T23:44:43.386157+00:00", "status": "completed" }, "tags": [] @@ -665,10 +1484,10 @@ "id": "ict-15b-empirical-exo2-md", "metadata": { "papermill": { - "duration": 0.002735, - "end_time": "2026-09-01T06:42:02.353282+00:00", + "duration": 0.003261, + "end_time": "2026-09-26T23:44:43.404314+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.350547+00:00", + "start_time": "2026-09-26T23:44:43.401053+00:00", "status": "completed" }, "tags": [] @@ -691,20 +1510,20 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "id": "ict-15b-empirical-exo2-stub", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:02.359646Z", - "iopub.status.busy": "2026-09-01T06:42:02.359646Z", - "iopub.status.idle": "2026-09-01T06:42:02.377207Z", - "shell.execute_reply": "2026-09-01T06:42:02.376698Z" + "iopub.execute_input": "2026-09-26T23:44:43.411597Z", + "iopub.status.busy": "2026-09-26T23:44:43.411385Z", + "iopub.status.idle": "2026-09-26T23:44:43.421171Z", + "shell.execute_reply": "2026-09-26T23:44:43.420306Z" }, "papermill": { - "duration": 0.022647, - "end_time": "2026-09-01T06:42:02.378618+00:00", + "duration": 0.014544, + "end_time": "2026-09-26T23:44:43.421960+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.355971+00:00", + "start_time": "2026-09-26T23:44:43.407416+00:00", "status": "completed" }, "tags": [] @@ -755,10 +1574,10 @@ "id": "ict-15b-empirical-exo3-md", "metadata": { "papermill": { - "duration": 0.002742, - "end_time": "2026-09-01T06:42:02.384918+00:00", + "duration": 0.002903, + "end_time": "2026-09-26T23:44:43.428110+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.382176+00:00", + "start_time": "2026-09-26T23:44:43.425207+00:00", "status": "completed" }, "tags": [] @@ -821,20 +1640,20 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "id": "ict-15b-empirical-exo3-stub", "metadata": { "execution": { - "iopub.execute_input": "2026-09-01T06:42:02.392020Z", - "iopub.status.busy": "2026-09-01T06:42:02.392020Z", - "iopub.status.idle": "2026-09-01T06:42:02.438661Z", - "shell.execute_reply": "2026-09-01T06:42:02.438070Z" + "iopub.execute_input": "2026-09-26T23:44:43.435734Z", + "iopub.status.busy": "2026-09-26T23:44:43.435492Z", + "iopub.status.idle": "2026-09-26T23:44:43.490452Z", + "shell.execute_reply": "2026-09-26T23:44:43.489436Z" }, "papermill": { - "duration": 0.051933, - "end_time": "2026-09-01T06:42:02.439958+00:00", + "duration": 0.059922, + "end_time": "2026-09-26T23:44:43.491285+00:00", "exception": false, - "start_time": "2026-09-01T06:42:02.388025+00:00", + "start_time": "2026-09-26T23:44:43.431363+00:00", "status": "completed" }, "tags": [] @@ -846,22 +1665,9 @@ "text": [ "=== Verdict de Cech recalcule sur les trajectoires de CE notebook ===\n", " gray_scott : TRIVIAL | s2/s1=1.0000 | rank=3 | cob=0.0000 | n_windows=30\n", - " axelrod : TRIVIAL | s2/s1=0.0572 | rank=1 | cob=0.0806 | n_windows=30\n", - " grokking : NON_TRIVIAL | s2/s1=0.6627 | rank=3 | cob=0.7767 | n_windows=30\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - " may : NON_TRIVIAL | s2/s1=0.4081 | rank=3 | cob=0.5992 | n_windows=30" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", + " axelrod : TRIVIAL | s2/s1=0.0000 | rank=1 | cob=0.0000 | n_windows=30\n", + " grokking : NON_TRIVIAL | s2/s1=0.6627 | rank=3 | cob=0.7767 | n_windows=30\n", + " may : NON_TRIVIAL | s2/s1=0.4081 | rank=3 | cob=0.5992 | n_windows=30\n", "\n", "Exercice a completer : table de comparaison + conclusion.\n" ] @@ -932,21 +1738,21 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.13.14" }, "papermill": { "default_parameters": {}, - "duration": 2.89415, - "end_time": "2026-09-01T06:42:02.685174+00:00", + "duration": 7.331272, + "end_time": "2026-09-26T23:44:43.842777+00:00", "environment_variables": {}, "exception": null, "input_path": "ICT-15g-EmpiricalHuangExploitation.ipynb", "output_path": "ICT-15g-EmpiricalHuangExploitation.ipynb", "parameters": {}, - "start_time": "2026-09-01T06:41:59.791024+00:00", + "start_time": "2026-09-26T23:44:36.511505+00:00", "version": "2.7.0" } }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/ict/dissociations-matrix.md b/docs/ict/dissociations-matrix.md index 020d66b3c8..bd8755d1cd 100644 --- a/docs/ict/dissociations-matrix.md +++ b/docs/ict/dissociations-matrix.md @@ -95,6 +95,7 @@ Conventions : | **ICT-15c** | Le **motif de désaccord** entre proxys est stable cross-substrat = obstruction informative (≠ dispersion d'échelle) | `s` ✓ | Vecteur d'obstruction `(a−b)/(\|a\|+\|b\|+ε)`, `mean_norm_L2`, verdict `STABLE`/`NOISE` | `f(x)=x%2` saturait (artefact plafonné) → `f(x)=x` (identité) restaure un panel non-plafonné | 4 substrats (Gray-Scott, Axelrod, Grokking, May) | proxy_interpretation | **Spéculatif** | **Candidat obstruction** (régime 2, *pas* érigé en obstruction cohomologique régime 3 — cf garde-fou A2). Verdict falsifiable `STABLE` si `mean_norm_L2 ≤ 0.05`. See [#7395](https://github.com/jsboige/CoursIA/issues/7395) | | **ICT-15d** | La cochaîne de Čech pondérée discriminate les substrats (`NON_TRIVIAL` = les proxys ne se recollent pas en 1D) | `s` ✓ | `mean_coboundary`, `s2_over_s1`, `effective_rank`, verdict `TRIVIAL`/`NON_TRIVIAL` | Sanity check + 4 substrats ICT-15c | 4 substrats × 3 proxys × fenêtrage | reproduced_in_toy_model | **Spéculatif** *(réserve de non-discrimination — voir [§ ICT-15d-corr](#note-ict-15d-corr--requalification-2026-08-21))* | 4/4 substrats `NON_TRIVIAL` sur la mesure courante ; l'« honnête négatif 0/4 » de la version précédente mesurait un `sensitivity.py` défectueux (réparé par [#9792](https://github.com/jsboige/CoursIA/pull/9792)). **Réserve** : la sortie reste dominée par la SVD (`s2_over_s1`, `effective_rank`) ; le contre-exemple `axelrod` (`mean_cocycle=0`, `obstruction_ratio=0`, `sign_consistency=0`) ne consulte ni le nerf ni H¹. Le verdict **ne porte pas** l'énoncé « Čech discrimine les substrats ». Issue [#7744](https://github.com/jsboige/CoursIA/issues/7744) closed-archived. | | **ICT-15e** | La trajectoire de réparation **est** un signal distinct d'un random walk calibré (recouvrabilité = agentivité, ≠ simple gain) | `s` ✓, `q` ○, `π` ○ | Distance de Mahalanobis sur signature 5-D, seuil p95 par permutation null | Random walk calibré sur mêmes extrema + 1 contrôle structural ; conjecture pré-enregistrée | 3 configs Gray-Scott (pearl/spots/seed=7) | reproduced_in_toy_model | **Fortement soutenu** | `CONFIRMED` sur substrats à dynamique réaction-diffusion informative (signature `slope_ratio` élevé) ; `FALSIFIED` sinon (random walk reproduit la structure) — verdict nuancé honnête, *pas* universel. See [#8077](https://github.com/jsboige/CoursIA/issues/8077) | +| **ICT-15g** (banc appris) | Sur l'hypercube **vrai** (degré de Fourier-Walsh RÉEL des tables réalisées, ≠ proxy de masse #9771), le ratio s_max/√deg des fonctions apprises par petits réseaux **chevauche** la bande des fonctions aléatoires de même degré ; l'entraînement adversarial discret (pire voisin Hamming-1, λ=0,5) ne produit **aucune baisse de s_max ni du ratio** dans les paires qualifiées de ce banc à 3-4 bits — critère **déclaré** (Δs_max, **proxy** de la quantité de l'issue) : 28 paires appariées, 27 Δs_max=0, 1 Δ>0, 0 Δ<0, médiane 0 → **FAIL** ; complément post-revue sur le ratio : 26 Δratio=0, 2 >0, 0 <0, médiane 0 (descriptif, sans critère déclaré) | `s` ✓ | ratio s_max/√deg avec s_max exact sur Q_n (définition de Huang, **distinct** de la sensibilité sur graphe de transition observé du banc hérité) ; Δratio apparié mesuré explicitement (post-revue) | Réseau non entraîné (même init) + bande aléatoire appariée en degré (n=3 exhaustive : 256/256 ; n=4 : 3000 tirages) ; garde anti-constance comptée honnêtement (17/80 = 21,2 % dégénérés, sous le seuil 1/3) | 4 RNG-seeds (0/1/7/42) × 5 cibles (OR/AND/MAJ/PAR/TRI_bal) × 2 régimes × n ∈ {3, 4} — plan d'analyse **déclaré** avec amendements de calibration documentés sur graines disjointes {11, 22} (pas d'horodatage indépendant externe) | reproduced_in_toy_model | **TESTÉ (FAIL du critère déclaré Δs_max, sans re-seuillage ; pas de déplacement du ratio détecté non plus)** | CPU 3-4 bits, MLP 16 cachées, 75 % couverture, budget fixe 400 étapes. La borne Huang **vérifiée mécaniquement** (0/118 tables réalisées, 256/256 à l'exhaustif n=3) — c'est le déplacement adversarial qui est non détecté, pas la borne qui tombe. **Grille λ testée : {0 (= normal), 0,5, 1}** : à λ=1 le terme adversarial bloque l'apprentissage (λ écarté à la calibration sur graines disjointes {11, 22} ; taux de dégénérés non conservé dans une sortie committée), à λ=0,5 aucun déplacement détecté ; les valeurs intermédiaires (0,5 < λ < 1) **non testées**. Chevauchement de bande **≠** indistinguabilité statistique (aucun test formel). Δs_max = 0 n'implique pas même table : une paire (n=4, TRI_bal_4, graine 7) garde s_max = 4 mais passe de deg 4 à deg 3 (Δratio = +0,309). Lecture bornée aux régimes testés, ne généralise pas aux grands n. Le statut « transposition conjecturale » du banc hérité (graphes de transition) est conservé. See [#17977](https://github.com/jsboige/CoursIA/issues/17977) | | **ICT-16** | `F` = partie résiduelle de `K` + bosse complexité-entropie | `s` ✓ | MDL two-part code | Modèles témoins sans bosse | RNG multi-source | established_external | **Établi** | Bosse Crutchfield-Feldman 1998 mesurée à H* ≈ 1.99 bits/symbole ; cave emptor : **un seul couple (modèle, famille de sources)** dans la figure rendue | | **ICT-17** | L'ε-machine (Crutchfield) ≠ Hoel : deux lectures de l'émergence causale | `s` ✓, `W` ○ | États causaux + complexité statistique + entropie d'excès | Hoel info effective, CE 2.0 | multi-substrat | conceptual_analogy | **Fortement soutenu** | Dissociation reconnue mais pas hiérarchisée | | **ICT-17b** | 2/5 proxys dissocient du progrès de généralisation pendant le grokking | `s` ✓, `q` ○ | ‖w‖², Fisher-MDL, 1-test_acc, trace de Fisher, pred-zlib | Baseline avant-grokking | 5 proxys | reproduced_in_toy_model | **Établi** | 3 co-localisent (‖w‖², Fisher-MDL, 1-test_acc) ; 2 dissocient (trace Fisher, pred-zlib). Verdict honnête dissociatif (#7268) |