Repository navigation
test(dimred): audit SpectralAutoencoder contracts and add missing tests (M2.5) - #87
Conversation
PhD-Brown
left a comment
There was a problem hiding this comment.
Revue scientifique indépendante — M2.5 / PR #87 — Claude (1ʳᵉ revue)
Verdict : MODIFICATIONS REQUISES
Deux problèmes bloquants (R1, R2) : la suite ne protège ni la non-linéarité du réseau, ni la restauration complète du meilleur état. Pour chacun, l'ajout est limité aux tests, a été validé localement et ne demande aucun changement de production. Les six autres points (R3–R8) sont non bloquants ; R3–R5 sont peu coûteux et souhaitables dans cette PR.
(La review est enregistrée en COMMENTED parce que le compte est partagé ; le verdict ci-dessus fait foi.)
1. SHA audité et delta
- HEAD de PR
ae1e2be0a4fd5f60ad39068b0b05ceb0ca645546(inchangé depuis la passation), basemain@795af2c8fbe61432dda44480013d86f34ed43e02. - Diff : exactement les 6 fichiers annoncés (+2457/−0). Blob
src/dimred/autoencoder.pyb06bfa75…identique sur la base et la tête : aucune modification de production, de dépendances ou de CI. - Snapshot
1eb0c0b: les 14 empreintes SHA256 du JSON de mutations correspondent au contenu Git du snapshot (CRLF pour les fichiers Windows, LF pour les fichiers de tests et le script). Le delta1eb0c0b → ae1e2bene touche que la matrice et le JSON. Archive59eb774accessible ; elle est le parent de la ligne d'intégration.
2. Résultats de tests reproduits (Linux, Python 3.11.18, venvs uv isolés et jetables)
Environnement fast : requirements/ci.txt, Torch absent (et non bloqué). Environnement torch : idem + torch==2.10.0+cpu. NumPy 2.4.6, sklearn 1.9.1, pytest 8.4.2, comme chez Codex.
| Commande | Résultat |
|---|---|
pytest -q tests/test_dimred_autoencoder.py -W error (fast) |
31 PASS |
pytest -q / --import-mode=importlib (fast) |
413 PASS / 3 SKIP / 58 deselected (les 3 skips sont Windows-only, comme en CI) |
pytest -q -m "integration and not slow" |
57 PASS |
pytest -q -m slow tests/test_dimred_autoencoder_real.py (fast) |
29 SKIP (sans Torch, ces skips n'ont aucune valeur de validation) |
| idem (torch), normal et importlib | 29 PASS |
Suite complète -m "slow or not slow" (torch) : ordre inversé, importlib inversé, pytest-randomly ×5 graines dont 2 avec --randomly-dont-reset-seed |
442 PASS / 32 SKIP à chaque fois : aucune dépendance à l'ordre |
black --check / ruff check (src tests scripts app.py + script d'audit) |
PASS / PASS |
pip check, git diff --check 795af2c ae1e2be |
PASS |
| CI 42541068481 | completed/success, head_sha = ae1e2be ; journal lu : 413 PASS / 3 SKIP / 58 deselected, Black 35 fichiers, Ruff, pip check |
Rappel : la CI officielle n'exécute pas les 29 tests réels. Leur validation repose sur ma reproduction locale et sur celle de Codex.
3. Validité des oracles
Solides et indépendants (recalculés à la main) :
- MSE par ligne [3, 3, 49/3] et globale ;
- résumé (0, 1, 4, 16) : moyenne 21/4, σ population √651/4 (≠ ddof 1), médiane 5/2, q95 linéaire 14,2 (≠ max 16), classes 2 et 9 non contiguës ;
- PCA sur axes orthogonaux : 14/9, 0, 5/9, 1/9 ;
- interpolation affine ; vote excluant −1 ; barycentres et analogies asymétriques ;
- comptages de paramètres (ex.
3→[4,2]→1= 84, buffers BN exclus) ; premier pas d'Adam p = 0,1/(1+ε).
Ils distinguent bien MSE/MAE, moyenne/médiane, moyenne/somme, ddof, rang du quantile et labels décalés.
Faiblesses :
- R1 : aucun oracle de valeur ne traverse un bloc caché (BN/ReLU/Dropout).
- R2 : trajectoire d'early stopping non discriminante, réseau contrôlé sans buffer.
- R3 : le premier pas d'Adam est invariant au clipping.
- R4/R5 : frontières k = 50 et log g supérieur non fixées.
- R6 : certains oracles rejouent l'implémentation (ordre du RNG, dtype float32, hyperparamètres du scheduler) et rejettent des variantes admissibles.
Fixture sans Torch :
- Elle n'injecte que des noms inertes (
Module = object) et ne peut construire aucun réseau ;torch.FloatTensory lèveraitAttributeError. - Les noms sont retirés de
sys.modulesdès la sortie deexec_module. Mon sondage ne montre aucune fuite detorch*ni dem25_numeric, etfind_spec("torch")resteNoneensuite. - Elle ne masque pas #21 : voir la section 5.
- Seul effet global persistant :
matplotlib.use("Agg", force=True)n'est pas restauré (déjà le cas danstest_dimred_pca_analyzer.py:736). Effet sans conséquence pour une suite headless, signalé pour mémoire.
4. Entraînement PyTorch
- Split :
- Ce qui est vérifié : effectifs, disjonction, couverture, cibles égales aux entrées,
shuffle=True, invariance à la graine Torch externe. - Il s'agit d'un split de lignes, pas d'objets. L'observation des doublons, reproduite, relève de #22.
- L'exposition de la validation interne au
StandardScalerglobal est confirmée (data_loader.py:319-322, celluleloader.load(scale=True)avant le fit dans le notebook). La matrice la qualifie correctement : exposition de la validation interne, pas preuve d'une fuite vers un test externe (#24).
- Ce qui est vérifié : effectifs, disjonction, couverture, cibles égales aux entrées,
- Optimiseur : oracle Adam correct. Reset des gradients et weight decay vérifiés par spy (
zero_grad×3, kwargs). No-grad en validation détecté indirectement par le critère contrôlé. Clipping : voir R3. - Scheduler : le plateau réel (12 × 0,01 puis 0,005) correspond à la sémantique
num_bad_epochs > patiencede Torch 2.10, stable depuis longtemps. Acceptable comme verrou de version, à documenter (R6). - Early stopping : le nombre d'epochs et le poids restauré (2) sont corrects. Mais voir R2 : remise à zéro de la patience et buffers BN non prouvés.
5. Preuves rouges (59eb774, rejouées dans une copie isolée)
11 FAIL / 14 PASS reproduits à l'identique, y compris 236.7083282 ≠ 228 pour #84 (même graine Torch 1729 sous Linux). Causes vérifiées :
- #21 ×4 :
NameError: nnà la l. 87, avant_check_torch. Je l'ai reproduit aussi avec Torch réellement désinstallé, pas seulement bloqué par le meta-path : import du module,import dimred,from dimred import PCAAnalyzeret mêmefrom dimred.pca_analyzer import …(via__init__). Le dispositif de blocage ne crée donc pas l'erreur. - #80 ×2 :
ValueError: Expected more than 1 value per channel when training. Précondition confirmée : 5 lignes d'entraînement avec des batchs de 4 donnent un dernier batch de 1, et N = 2 ne laisse qu'une ligne d'entraînement. - #81 ×1 : sortie différente après un
batch_size=0refusé ; - #82 ×2 :
{'train_loss': [nan], 'val_loss': [nan]}renvoyé comme succès ; - #83 ×1 : 4
Linearau lieu de 1 ; - #84 ×1 : sonde de politique.
Les 11 FAIL correspondent à 5 défauts comportementaux + 1 contrat ouvert, et non à 11 bugs. Les 14 PASS sont des observations, pas des politiques validées. Les 7 contrôles de remèdes donnent 7 PASS en direct. L'écriture finale du JSON échoue seulement parce que ma copie n'est pas un dépôt Git : c'est un artefact d'environnement.
Limites des oracles rouges : voir R8 (#80 accepte un drop_last silencieux qui produit train_loss=nan ; #81 ne contrôle pas les métadonnées).
6. Mutations de Codex
Le script est intègre :
- chaque ancre doit exister (sinon
ValueError) et le code muté est compilé ; - un baseline de 56 tests sert de référence ;
- les erreurs, skips et tests manquants sont classés INCONCLUSIVE ;
- les empreintes sont vérifiées avant et après ;
- les 4 tests exclus via
-ksont bien exécutés dans les suites normales.
Rejoué entièrement (4 min 24) : 40/40 cas identiques au JSON (statut, nombre de remplacements, nombre de tests, noms des tests en échec). Nuance : certaines mises à mort viennent de crashs ou d'artefacts de spy, et non d'oracles de valeur (R7).
7. Mutations indépendantes (même harnais, 56 tests)
| Mutant | Erreur | Oracle censé le détecter | Résultat |
|---|---|---|---|
| C01b | buffers BN non restaurés (named_parameters + strict=False) |
early stopping | SURVIVED → R2 |
| C02 | patience jamais remise à 0 | early stopping | SURVIVED → R2 |
| C03 | ReLU → Identity (AE linéaire) | architecture/forward | SURVIVED → R1 |
| C04 | ReLU avant BN | architecture/forward | SURVIVED → R1 |
| C05 | clipping après step |
optimiseur | SURVIVED → R3 |
| C06 / C07 | k = 60 / k = 20 voisins | vote candidat | SURVIVED → R4 |
| C08 | logg <= g_hi |
arithmétique latente | SURVIVED → R5 |
| C09 | lignes 0/1 permutées dans per_sample |
résumé par classe | KILLED |
| C10 | quantile method='nearest' (médiane inchangée) |
résumé | KILLED |
| C11 | val_loss calculée sur les lignes d'entraînement |
split (spy) | KILLED |
| C12 | decode en mode train |
dropout/roundtrip | KILLED |
| C13 | load sans buffers BN |
roundtrip | KILLED |
| C14 | entraînement en mode eval | split (spy) | KILLED |
| C15 | labels décalés (np.roll) |
résumé | KILLED |
| C16 | restauration de l'état de la dernière epoch | early stopping | KILLED |
| C17 | MSE candidate dans l'espace brut | candidat | KILLED |
Tous les mutants étaient actifs (ancre trouvée, compilation, baseline PASS) ; aucun n'est équivalent. Les oracles proposés dans R1–R5 ont été écrits et exécutés dans une copie jetable : 6/6 PASS sur la production, et chaque survivant tué.
8. Compatibilité avec les correctifs légitimes
Acceptés par la suite verte :
- honorer
hidden_dims=[]; - invalidation complète après un échec ;
- refus des non-finis ;
- perte d'epoch pondérée par les effectifs (contrôle réel, pas un simple
float()) ; drop_lastlimité au batch singleton ;- les contrôles de Codex (métadonnées de checkpoint supplémentaires, prose prudente, refus 2D explicite).
Rejetés à tort (R6) : validation triée (même ensemble), sortie float64, RNG indépendant par population, autre patience du scheduler.
9. Qualification des issues
| Issue | Conclusion |
|---|---|
| #21 | Confirmée (Torch réellement absent + blocage ; façade et sous-module PCA aussi touchés) |
| #80 | Confirmée avec qualification : seulement en entraînement, avec un batch de taille 1 ; l'oracle accepte un remède silencieux (R8) |
| #81 | Confirmée ; la preuve ne vérifie pas les métadonnées (R8) |
| #82 | Confirmée ; l'imputation silencieuse passe l'oracle, c'est une décision humaine |
| #83 | Confirmée (P2 adéquat) |
| #84 | Contrat de métrique ouvert, correctement classé ; la valeur exacte dépend de la composition des batchs (RNG de shuffle), mais l'écart est structurel (batchs 4/3). Aucun test vert n'impose de choix |
| #85 | Confirmée (documentaire) ; aucun test vert ne fige verdicts ni seuils |
| #86 | Risque statique correctement qualifié ; aucun exploit. Je n'ai chargé que des checkpoints synthétiques générés localement par les tests |
| #22 / #24 | Liens corrects ; portée correctement limitée |
10. Limites scientifiques
Comme le dit déjà la matrice, rien ici ne valide :
- une interpolation latente comme chemin évolutif ;
- les seuils 0,97/3,0 comme quantiles ;
- les analogies comme lois ;
- une supériorité sur PCA.
Les tests verts restent compatibles avec une prose prudente. Conserver cette réserve après les corrections.
11. Corrections requises (tests et documents d'audit uniquement)
- R1 (bloquant) : oracle manuel en mode eval à travers un bloc caché (Linear→BN→ReLU→Dropout).
- R2 (bloquant) : trajectoire avec amélioration tardive et buffer contrôlé dans l'early stopping.
- R3 : spy sur
Adam.step(norme ≤ 1). R4 : frontière k = 50. R5 : lignes log g = 3,0 et 5,5. - R6 : assouplir ou documenter les verrous de reproductibilité.
- R7 : corriger les revendications AP de la matrice ; ajouter C01b–C08 au script de mutations.
- R8 : documenter les limites des oracles rouges et reporter des critères renforcés sur #80/#81 (archive inchangée).
12. Verdict
MODIFICATIONS REQUISES. M2.5 reste 🟡. Corrections par Codex sur la même branche et la même PR ; seconde revue ciblée ensuite. Aucun commit, merge, passage en Ready for review ni travail M2.6 de ma part.
Generated by Claude Code
PhD-Brown
left a comment
There was a problem hiding this comment.
Seconde revue scientifique indépendante — M2.5 SpectralAutoencoder
Verdict : APPROUVER — prête pour la décision humaine de fusion
Les deux points bloquants (R1, R2) et les six autres (R3–R8) sont corrigés et vérifiés de façon indépendante. Je n'ai trouvé aucune régression. Deux réserves non bloquantes subsistent ; elles sont déjà déclarées dans la matrice (section 11). (Review enregistrée en COMMENTED parce que le compte est partagé ; le verdict ci-dessus fait foi.)
1. SHA
- Ancien HEAD examiné :
ae1e2be0a4fd5f60ad39068b0b05ceb0ca645546. Nouveau HEAD :047e2c79048b0df47245bebbf5544d439a9acdcf, inchangé au moment de la revue. - Base :
main@795af2c, inchangée. - Blob
src/dimred/autoencoder.py:b06bfa75identique sur la base, l'ancienne tête et la nouvelle. - Snapshot de campagne :
05d94d3. Archive rouge :59eb774, accessible et immuable.
2. Diff examiné
3 commits (05d94d3, 2dc41b5, 047e2c7) sur 9 fichiers (+3608/−60), soit 10 fichiers par rapport à la base. Aucun fichier de production, de dépendance, de CI ni de notebook.
tests/: nouvel oracle caché R1, trajectoires et buffer R2, spy au pas R3, population 20/50/60 R4, bornes log g R5, assouplissements R6.conftest.pyrestaure aussiwarn_only.audit/: harnais mis à jour (schéma 2, types de détection,--tests-ref), deux nouveaux JSON de mutations, checker R8 et son JSON, révision de la matrice.- Le fichier de preuves rouges reste hors de l'arbre vert.
3. Statut R1–R8
| # | Statut | Preuve principale |
|---|---|---|
| R1 | VÉRIFIÉ — RÉSOLU | Recalcul manuel −2,0 / 0,0. C03 → −5,5, C04 → −3,0 (assertion numérique) |
| R2 | VÉRIFIÉ — RÉSOLU | C02 → longueur 4 au lieu de 5 ; C01b → seen_batches 5 au lieu de 3 (assertion, pas une exception de clés) |
| R3 | VÉRIFIÉ — RÉSOLU | C05 → norme consommée 58,5 > 1+1e−5 ; spies passifs (référence identique bit à bit) |
| R4 | VÉRIFIÉ — RÉSOLU | Effectifs 15/5, 26/24, 34/26 ; C06/C07 → 2 == 7 |
| R5 | VÉRIFIÉ — RÉSOLU | C08 → la ligne log g 5,5 rejoint K naine ; échec de l'assertion d'appartenance |
| R6 | VÉRIFIÉ — RÉSOLU | A01/A02/A03/A09 PASS ; protections intactes (réserve 11b) |
| R7 | VÉRIFIÉ — RÉSOLU | AP limités, types de détection, empreintes conformes |
| R8 | VÉRIFIÉ — RÉSOLU | Checker rejoué : 6 PASS / 15 cas ; #80–#82 fidèles |
Chaque fil inline a reçu une réponse détaillée et a été résolu.
4. Rejeu des huit anciens survivants
Avec mes remplacements exacts de la première revue (même harnais, 60 tests révisés) : C01b, C02, C03, C04, C05, C06, C07 et C08 sont tous tués par une assertion pertinente, sans erreur ni exception de fixture. Mes 9 autres mutants (C09–C17) restent tués : 17/17 au total. Le JSON historique de Codex (--tests-ref ae1e2be, empreintes des tests de ae1e2be vérifiées) reproduit exactement mon constat initial : 8 SURVIVED / 4 FAIL.
5. Alternatives admissibles
- A01 (validation triée), A02 (float64), A03 (RNG indépendant par population), A09 (patience 5) : 4 PASS. Mes autres contrôles passent aussi :
[]honoré, invalidation complète, refus des non-finis, pertes pondérées réelles,drop_lastlimité au singleton. Total : 10/10. - L'assouplissement n'a pas affaibli la détection. Mutants inédits, tous tués :
- barycentre médian ;
- tirage avec remise (127 lignes uniques au lieu de 200) ;
- scheduler alimenté par
train_loss; factor=1.0.
6. Campagne de mutations
- Harnais : chaque ancre composée doit exister ; la double application de C01b ne se produit qu'une fois ; type de détection
assertion/runtime_exception; tests de refus attendus comptés à part. - JSON révisé : 41 KILLED / 0 SURVIVED / 0 INCONCLUSIVE ; 13 admissibles + 2 équivalents + baseline PASS, 60 cas par essai, 14/14 empreintes conformes à
05d94d3, arbre propre au début et à la fin. - Les 5 mises à mort non fondées sur une assertion (
checkpoint_*,wrong_latent_dim,reversed_rows,validation_train_mode) sont présentées comme telles dans la matrice. - Je n'ai pas relancé la campagne complète : entrées identiques, résultats vérifiés cas par cas, et mes candidats rejoués indépendamment.
7. Preuves rouges et leurs limites
- Archive
59eb774inchangée ; ses 11 FAIL / 14 PASS ont été rejoués lors de la première revue, au même SHA immuable. - Checker R8 rejoué sous Linux : 6 PASS / 15 cas, empreintes de l'archive et du checker identiques.
- Les commentaires sur #80, #81 et #82 sont fidèles et ne revendiquent aucun correctif.
8. Suite sans Torch (Linux, Python 3.11.18, venv uv jetable, Torch non installé)
pytest -q tests/test_dimred_autoencoder.py -W error: 31 PASS.pytest -qet--import-mode=importlib: 413 PASS / 3 SKIP / 62 deselected. Les 3 skips sont réservés à Windows ; Codex obtient 416/62 sous Windows.-m "integration and not slow": 57 PASS.- Black (42 fichiers, scripts
audit/inclus), Ruff,pip check(deux environnements),git diff --check 795af2c 047e2c7: PASS.
9. Tests PyTorch réels (Torch 2.10.0+cpu, NumPy 2.4.6, sklearn 1.9.1, pandas 3.1.1, pytest 8.4.2)
-m slow tests/test_dimred_autoencoder_real.py, normal et importlib : 33 PASS.- Suite complète
-m "slow or not slow", en ordre normal, inversé et avecpytest-randomlysur 2 graines sans réinitialisation des RNG : 446 PASS / 32 SKIP à chaque fois. Le total de 478 est égal à celui de Codex (449/29 sous Windows).
10. CI au SHA final
Run 42548715421 : completed/success, head_sha = 047e2c7. Journal lu : 413 PASS / 3 SKIP / 62 deselected, Black, Ruff, pip check. Les 33 tests réels restent hors CI officielle ; leur validation repose sur les rejeux locaux de Codex (Windows) et les miens (Linux).
11. Risques encore ouverts (non bloquants)
- a. Non-linéarité du décodeur : supprimer ReLU uniquement dans
_Decoderpasse toute la suite (64 PASS). La matrice le déclare en PA, et ma proposition R1 ne visait que l'encodeur. Un oracle miroir sur le décodeur est un ajout simple, à faire en suivi ou avant fusion selon la décision humaine. - b. Échantillonnage de l'arithmétique latente : l'observation dépend de
np.random.default_rng().choice. Deux variantes admissibles restent rejetées : pas de sous-échantillonnage sous le plafond, tirage parpermutation. C'est documenté dans la matrice ; la suggestion figure dans le fil R6. - Politiques scientifiques ouvertes, hors PR :
- Aucune validation astrophysique, aucune donnée réelle, aucun GPU, aucun notebook exécuté.
12. Verdict final
APPROUVER — prête pour la décision humaine de fusion. M2.5 reste 🟡 jusqu'à la fusion effective. Je ne fusionne pas la PR et je ne la passe pas en Ready for review. Aucun commit ni travail M2.6 de ma part.
Generated by Claude Code
Refs #43 — R1–R8 traitées après la première revue Claude, pour seconde revue indépendante sur cette même Draft PR.
Tête finale :
audit/43-dimred-autoencoder@047e2c79048b0df47245bebbf5544d439a9acdcf.Base :
main@795af2c8fbe61432dda44480013d86f34ed43e02.Entrées du rejeu complet et des suites :
05d94d327028627e7b8d38e74be47b5550c21537.Checker R8 final :
2dc41b5b22d1bc457fdd076988dcdf21a3c30e33.Dernier commit : documents/résultats uniquement. Matrice révisée.
Aucun changement de production, dépendances, CI, notebooks, données ou modèles. Blob AE inchangé :
b06bfa75a2e0a8b132d0a0fb8e0a899b6afed0ad. Worktree principal intact ; travail dans le worktree M2.5. Checkpoints poussés et SHA distants vérifiés après chaque bloc : A046e78d, B0374bf3, Cd944ca5, R69a7dab4, D05d94d3, puis sauvegarde des preuves 2dc41b5 et tête ci-dessus.Révision des contrats
Mutations et admissibilité
Rejeu révisé : 41 incorrectes KILLED, 0 SURVIVED/INCONCLUSIVE ; 13 contrôles admissibles + 2 équivalents + baseline PASS, 60 tests par essai. Aucun skip ni erreur de collecte/fixture.
Rejeu des anciens tests, avec
--tests-ref ae1e2be: les 8 anciens survivants SURVIVED et les 4 variantes admissibles FAIL sont reproduits ; baseline56 PASS. L'exit1 est attendu pour cette comparaison historique.Les huit nouveaux contre-exemples sont détectés par assertions. Les détections de checkpoints corrompus sont des exceptions de chargement (2 chacune), pas des oracles numériques ;
validation_train_modedonne 8 assertions de frontière/état et 1 exception BN. L'ancien artefact IndexError est remplacé par un contrôle explicite du mode d'évaluation. Messages/types/tracebacks, versions, SHA256 effectifs et états Git réels sont enregistrés.Archive et qualifications
59eb774eb618cdf639cde3c0358da319ee4bcffainchangée, accessible et ancêtre de la tête ; 11 FAIL/14 PASS historiques non réécrits. L'ancien JSON de mutations reste inchangé également.Checker R8 et résultats : 6 PASS / 15 cas pytest, sur copies temporaires uniquement.
Vérifications exécutées
Windows/Python3.11.9, NumPy2.4.6, pandas3.1.1, sklearn1.9.1, pytest8.4.2 ; Torch 2.10.0+cpu, cuda=None, venv isolé existant.
-W errorpytest -qet--import-mode=importlib-m "integration and not slow"-m slow tests/test_dimred_autoencoder_real.pyCI finale 42548715421 completed/success sur
047e2c79048b0df47245bebbf5544d439a9acdcf. Journaux lus : merge de contrôle536c1e0, Linux/Python3.11.18, 413 PASS /3 SKIP Windows /62 deselected /7 avertissements Gaia préexistants ; pip check, Black35fichiers et Ruff PASS. Les33tests CPU restent exclus de la CI officielle inchangée. Aucun GPU, catalogue réel, W&B réel, notebook exécuté, mesure de couverture ou validation astrophysique générale.Les huit réponses inline sont publiées et vérifiées : R1 · R2 · R3 · R4 · R5 · R6 · R7 · R8. Les huit fils restent ouverts pour Claude. Reprise du2026-11-09 : worktree propre, HEAD local/distant identiques,14empreintes de campagne et hashes historiques/R8 revérifiés. Aucun nouveau test/campagne ni commit inutile ; seules les vérifications de reprise et la finalisation GitHub ont été effectuées. Draft maintenue, M2.5 🟡, aucun merge ni M2.6.