Journal
Sections
Réglages de lecture
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
Code et termes techniques en anglais
Désactivé : la voix de la page lit tout, sans pause entre deux voix.
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
D’autres voix : Microsoft Edge propose des voix naturelles en ligne, et Windows (Paramètres › Heure et langue › Voix), macOS et Android en installent d’autres.
Progression
Section intitulée « Progression »- IX cloné et épinglé au commit
490c395; le code du cours dépend de sept de ses crates - Données :
builds.csvetjobs.csv, extraits de l’historique CI et de l’historique Git de ce dépôt - CI : formatage, clippy, tests unitaires et chaque exemple comparé avec
expected/sur trois OS, plus une vérification croisée avec numpy et scikit-learn sous Linux - Leçon 1 : données, caractéristiques et évaluation
- Leçon 2 : régression linéaire et descente de gradient
- Leçon 3 : classification
- Leçon 4 : partitionnement
- Leçon 5 : composantes principales
- Leçon 6 : ensembles
- Leçon 7 : réseaux de neurones
- Leçon 8 : optimisation
- Leçon 9 : cinq autres réducteurs, avec une MDS écrite à la main et des contrôles indépendants
- Annexe : la carte d’API, générée depuis le commit épinglé
- Leçons 10 à 21
- Traductions française et espagnole
IX est la bibliothèque de quelqu’un d’autre, épinglée à 490c395. Les leçons précédentes ont recensé dix-neuf constats ; la leçon 9 ajoute deux constats d’API ou de documentation. La première colonne indique ce qu’attendrait un appelant. Aucun constat n’est déclaré comme ticket IX. Plusieurs sont des choix défendables plutôt que des défauts, et la colonne du milieu dit lesquels.
| Attendu | Ce qui se passe | Où | Mesure | État |
|---|---|---|---|---|
| Les lignes de test ne participent pas au façonnage du prétraitement | Avec normalize, le pipeline ajuste StandardScaler et la PCA sur toutes les lignes, puis découpe |
ml_pipeline.rs 190-203 |
La première ligne de test vaut 1,440 contre 1,042 si le découpage vient d’abord | Reproduit, non déclaré 2026-09-14 |
| Un vecteur d’étiquettes entières est une cible de régression sauf mention contraire | infer_task_type qualifie de classification tout vecteur d’entiers positifs comptant au plus 20 valeurs distinctes |
preprocessing.rs 234-254 |
Vingt valeurs distinctes, c’est toute la règle | Reproduit, non déclaré 2026-09-14 |
| Une égalité entre plus proches voisins se tranche comme chez scikit-learn | max_by_key rend le dernier maximum : l’égalité va donc à l’indice de classe le plus grand |
knn.rs 53 |
Exactitude 0,947 contre 0,974 | Reproduit, non déclaré 2026-09-14 |
| Un cluster k-means vide est réensemencé, comme le fait scikit-learn | La mise à jour des centroïdes part de zéros et saute les clusters sans lignes : le centroïde reste à l’origine | kmeans.rs 137-152 |
KMeans(3) sur [5, 5, 9, 9] donne [5.0, 9.0, 0.0] |
Reproduit, non déclaré 2026-09-14 |
| La silhouette d’une ligne seule dans son cluster vaut 0, comme la définissent Rousseeuw et scikit-learn | silhouette_score_exact pose a = 0, d’où s = 1 |
ix-voicings/lib.rs 664-678 |
0,9296 contre 0,5963 | Reproduit, non déclaré 2026-09-14 |
| Les lignes d’import des tutoriels compilent | use ix_unsupervised::{KMeans, Clusterer}; échoue en E0432 : la crate n’exporte que des modules |
ix-unsupervised/lib.rs 5-14 |
E0432 |
Reproduit, non déclaré 2026-09-14 |
KMeans::fit fait plusieurs départs et garde le meilleur |
Il fait un unique départ k-means++ ; l’outil MCP ix_kmeans fixe en plus la graine à 42 |
kmeans.rs, handlers.rs 322 |
Un seul départ | Reproduit, non déclaré 2026-09-14 |
ix_voicings::cluster compare les k qu’il essaie |
Il garde k = 5 dès que sa silhouette atteint 0,15, et n’essaie k = 3 qu’en dessous |
ix-voicings/lib.rs 770-776 |
Un seuil, pas une comparaison | Reproduit, non déclaré 2026-09-14 |
| Un mélange gaussien se prémunit contre l’effondrement d’une composante | GMM plancher chaque variance à 1e-6, ce qui laisse une composante s’effondrer sur un point |
gmm.rs 170-171 |
1e-6 |
Reproduit, non déclaré 2026-09-14 |
explained_variance_ratio est une part de la variance totale |
Chaque valeur propre est divisée par la somme des valeurs propres conservées : les ratios somment donc toujours à 1 | pca.rs 46-57 |
[0,5635, 0,4365] contre les vraies [0,3867, 0,2996], dont la somme fait 0,6863 |
Reproduit, non déclaré 2026-09-15 |
| L’itération de la puissance trouve l’axe principal | Elle part du vecteur fixe (1, …, 1)/√n, et peut rendre l’axe secondaire |
pca.rs 107 |
Un départ qui n’a rien d’aléatoire | Reproduit, non déclaré 2026-09-15 |
| Les valeurs propres sont relisibles | PCA::explained_variance est privé et le ratio est normalisé comme ci-dessus : save_state() est la seule sortie |
pca.rs |
Un seul accès, et c’est un sérialiseur | Reproduit, non déclaré 2026-09-15 |
| Une forêt aléatoire tire ses variables à chaque coupe, comme son propre commentaire le promet | Elle les tire une fois par arbre, avant de le construire | random_forest.rs 68-84 |
Avec max_features = 1 : version à la main 0,9474, IX 0,7632 |
Reproduit, le commentaire et le code se contredisent, non déclaré 2026-09-15 |
| Une égalité dans le vote de la forêt se tranche comme chez scikit-learn | max_by garde le dernier maximum : elle va à l’indice de classe le plus grand |
random_forest.rs 97 |
Même forme que le constat 3 | Reproduit, non déclaré 2026-09-15 |
| Le gradient analytique égale un gradient par différences finies | Dense::backward divise une seconde fois par la taille du lot : grad_output porte déjà le 1/n que mse_gradient y a mis |
ix-nn/layer.rs 40-52 |
Rapport de 65,0000 exactement face à l’oracle par différences finies | Reproduit, non déclaré 2026-09-15 |
mse_gradient est le gradient de mse_loss |
Il l’est pour une colonne de sortie. mse_loss moyenne sur les lignes et les colonnes ; mse_gradient ne divise que par les lignes |
ix-nn/loss.rs 6-16 |
Rapports 1, 2, 3, 4 pour 1 à 4 colonnes | Reproduit, non déclaré 2026-09-15 |
Dense::new accepte une graine |
Il tire du générateur du thread : un réseau n’est donc pas reproductible | ix-nn/layer.rs 27 |
Aucun paramètre de graine | Reproduit, non déclaré 2026-09-15 |
Un Sequential de deux couches Dense peut apprendre le XOR |
Dense est le seul type qui implémente Layer : il n’existe aucune activation à glisser entre deux applications affines, et tout le réseau est une seule application affine |
ix-nn |
La perte plafonne à 0,25 après 50 000 époques et prédit 0,5 aux quatre coins | Reproduit, fonctionnalité manquante plutôt que défaut, non déclaré 2026-09-15 |
minimize distingue une exécution divergée d’une exécution à court d’itérations |
Il rend le point de départ de l’appelant, étiqueté comme le meilleur vu, avec le même converged: false qu’une non-convergence ordinaire |
ix-optimize/gradient.rs 124-165 |
Rosenbrock au pas 0,01 rapporte best f 24.200000, la valeur au départ |
Reproduit, non déclaré 2026-09-15 |
TSNE::transform(new_rows) place ces lignes sur la carte ajustée |
L’argument est ignoré : la méthode rend l’embedding 12 × 2 mémorisé même pour une entrée 1 × 1 |
tsne.rs 249-253 |
La sortie est exactement l’embedding d’origine | Reproduit, non déclaré 2026-09-24 |
| L’en-tête Barnes-Hut du module t-SNE décrit l’implémentation | compute_q et le gradient parcourent toutes les paires de points |
tsne.rs 160-178, 205-217 |
Boucles quadratiques visibles dans le code ; débit non mesuré | Confirmé dans le code, non déclaré 2026-09-24 |
Expériences
Section intitulée « Expériences »Un cours qui trouve des divergences doit montrer ce qu’il a vérifié et trouvé juste. Les six contrôles initiaux restent ci-dessous ; le cinquième a échoué et produit les constats 15 et 16. La leçon 9 ajoute deux contrôles géométriques exploratoires, non pré-enregistrés.
| Question | Hypothèse | Résultat | Verdict | Où |
|---|---|---|---|---|
Les SGD, Momentum et Adam d’IX suivent-ils pas à pas des implémentations écrites à la main ? |
Une implémentation correcte doit coïncider exactement, pas approximativement | Mêmes nombres de pas — 5000, 4129, 2822 — mêmes points finaux et même premier pas | Confirmée | 2026-09-15 |
La décomposition propre de la PCA d’IX coïncide-t-elle avec Jacobi et la SVD de scikit-learn ? |
Sous-entendue par les cinq variances qu’elle affiche | Jacobi à 1,28e-10, scikit-learn aux décimales affichées | Confirmée — seuls le ratio et le départ fixe sont faux | 2026-09-15 |
GradientBoostedClassifier coïncide-t-il avec une version à la main et un contrôle numpy ? |
Sous-entendue | Il coïncide sur chaque ligne de test à chaque nombre de tours, avec les mêmes log-priors lissés et les mêmes souches au premier tour | Confirmée | 2026-09-15 |
Un xorshift 64 bits rejouable peut-il remplacer le StdRng non rejouable d’IX dans un contrôle par bootstrap ? |
Écrite à l’avance : un générateur rejouable employé à l’identique en Rust et en Python retombe sur les mêmes lignes hors sac | Le contrôle numpy retombe sur les mêmes 48 lignes laissées de côté | Confirmée | 2026-09-15 |
Le gradient analytique d’ix_nn égale-t-il son gradient par différences finies ? |
Écrite à l’avance : le rapport doit valoir 1 | 65,0000, puis 1, 2, 3, 4 selon le nombre de colonnes — constats 15 et 16 | Réfutée | 2026-09-15 |
| La sommation flottante sur macOS-ARM change-t-elle une affirmation des leçons ? | Écrite à l’avance : seul le signe d’une valeur déjà nulle doit différer | -0.0000 contre 0.0000 à deux coins, toutes les autres valeurs identiques à quatre décimales (exécution 35039180659) |
Confirmée | 2026-09-16 |
| La MDS classique écrite à la main conserve-t-elle les mêmes distances du carré qu’IX ? | Les deux devraient retrouver les six distances, quelle que soit l’orientation des axes | Erreur maximale inférieure à 1e-10 pour les deux ; numpy concorde |
Confirmée, exploratoire | 2026-09-24, l09_reducers.rs |
| Le premier axe de la PCA à noyau RBF sépare-t-il des anneaux concentriques ? | La similarité non linéaire devrait placer le contraste radial en premier | IX et scikit-learn le placent sur l’axe 4 ; écart de 0.5798 dans IX, nul sur l’axe 1 |
Réfutée, exploratoire | 2026-09-24, l09_reducers.rs |
2026-09-14 — IX, épinglé
Section intitulée « 2026-09-14 — IX, épinglé »- IX est cloné à part de ma copie de travail, avec
git clone --filter=blob:none, et extrait à490c39533627d296bf9f8f050e6fafc14d7a20c2, un commit demaindu 2026-09-14, 21:40 UTC. Le workspace a 83 dossiers souscrates/(son README annonce 81 crates) ; le cours litix-math,ix-supervised,ix-optimize,ix-unsupervised,ix-voicings,ix-ioetix-agent, et dépend des cinq premières. Cargo.tomlnomme chaque crate avecgitetrev, etCargo.lockest commité. Cargo récupère une seule fois tout le dépôt IX pour les cinq crates, y compris son sous-modulegovernance/demerzel.- Le code du cours partage
ndarray0.17 avec IX : les matrices passent des versions à la main aux fonctions d’IX sans conversion. - Les outils MCP d’IX de ma session Claude Code m’ont aidé à trouver les algorithmes ; chaque résultat des leçons vient des exemples compilés, pas des outils.
2026-09-14 — Les données
Section intitulée « 2026-09-14 — Les données »data/extract.pylitcode/duckdb/data/runs.jsonetjobs.json, l’export que le cours DuckDB a fait le 2026-09-14, et exécutegit ls-treeà chaque commit construit pour compter ses fichiers.mdet.mdx.builds.csv: les jobsbuildréussis de Deploy to GitHub Pages, 65 lignes, avec les secondes de l’étape dont le nom commence par Install, build.jobs.csv: les jobs terminés en succès ou en échec qui ont à la fois une étape de checkout et son étape post, 186 lignes. L’OS est le label du runner sans-latest.queue_sest le temps entre la création du job et son démarrage.- Chaque durée est un nombre entier de secondes (la différence de deux horodatages, tronquée). La leçon 1 montre ce que cela fait à l’inférence de tâche d’IX, la leçon 4 ce que cela fait à un mélange gaussien.
- Aucun jeu de données n’a été téléchargé : les deux fichiers sont dérivés de ce dépôt public.
2026-09-14 — La CI
Section intitulée « 2026-09-14 — La CI »ml-ix-examples.ymlexécutecheck.shsurubuntu-latest,windows-latestetmacos-latest:cargo fmt --check,cargo clippy --release --all-targets -- -D warnings,cargo test --release(tests unitaires et doctests, y compris l’importcompile_failde la leçon 4), puis chaque exemple, dont la sortie et le code de sortie sont comparés avecexpected/pardiff --strip-trailing-cr.UPDATE=1réécritexpected/,CROSSCHECK=1ajoute la vérification Python en local.- La vérification croisée ne tourne que sous Linux, avec Python 3.13, numpy 2.4.2 et scikit-learn 1.8.0 : elle recalcule les métriques, la droite de régression, l’arbre de décision, les exactitudes des k plus proches voisins, les silhouettes, DBSCAN et un mélange gaussien, et compare sa sortie avec
expected/crosscheck.txt. Elle ne peut pas rejouer le générateur de nombres aléatoires d’IX, et ne reproduit donc ni les départs de k-means d’IX ni ses découpages aléatoires. - Les valeurs à virgule flottante sont affichées avec un nombre fixe de décimales (
fmt_vecdanssrc/lib.rs), si bien que les sorties sont identiques sur les trois systèmes ; aucune n’a eu besoin d’un fichier par OS. - Les exécutions 34903197003 (les exemples des leçons, commit
d9ef7fb) et 34903462618 (les solutions des exercices, commit15cde43) ont réussi sur les quatre jobs dès le premier push.
2026-09-14 — Choix faits en écrivant le code
Section intitulée « 2026-09-14 — Choix faits en écrivant le code »- Le
train_test_split, le k-means++, le départ du GMM et leStratifiedKFoldd’IX tirent leurs nombres duStdRngde Rust, que Python ne peut pas rejouer. Pour ceux-là, la version à la main part du résultat ajusté d’IX et vérifie qu’un pas de plus ne change rien (k-means, EM), et la vérification croisée compare ce qui ne dépend pas du départ : tailles, meilleures inerties sur n départs, log-vraisemblances. - La leçon 3 teste sur un job sur cinq, un découpage que les deux langages savent faire.
- La leçon 3 exécute
k = 4à côté dek = 5pour le départage des égalités d’IX : l’égalité que j’avais d’abord vue a disparu quand le jeu de test est devenu un job sur cinq, etk = 4standardisé en montre de nouveau une, sur une vraie ligne de test. - La première comparaison de mélange gaussien ne correspondait pas à scikit-learn. En remontant la piste, je suis tombé sur les variances effondrées de
complete_s, qui sont devenues une section de la leçon 4 au lieu d’une note de bas de page.
2026-09-14 — Là où IX diffère
Section intitulée « 2026-09-14 — Là où IX diffère »Neuf endroits où la réponse d’IX, ou sa documentation, diffère du manuel ou de scikit-learn, chacun montré par du code compilé dans le cours sauf mention contraire. Aucun n’a fait l’objet d’une issue IX.
- Mise à l’échelle avant le découpage. Avec
normalizeactivé, le pipeline ML ajusteStandardScaler(et la PCA) sur toutes les lignes, puis découpe : les lignes de test façonnent la mise à l’échelle (ml_pipeline.rs, lignes 190-203, puis 537-538 et 704-705). Lu dans le code ; le mécanisme est montré dans la leçon 1 (première ligne de test à 1.440 contre 1.042), l’outil lui-même n’a pas été exécuté. - Les cibles entières deviennent des classes.
infer_task_typequalifie de classification tout vecteur d’étiquettes entières positives ou nulles ayant au plus 20 valeurs distinctes (preprocessing.rs, lignes 234-254) : les secondes de build donnentMulticlassClassification { n_classes: 18 }, et ajouter 0.5 à une ligne donneRegression. Le pipeline l’utilise quand la tâche vautauto. - Égalités des k plus proches voisins.
KNN::predictutilisemax_by_key, qui renvoie le dernier maximum : une égalité va au plus grand indice de classe (knn.rs, ligne 53). Sur les jobs standardisés aveck = 4, la ligne de test 27 vote[2, 0, 2]: IX dit macOS, la version à la main et scikit-learn Ubuntu, le vrai OS ; exactitude de 0.947 contre 0.974. - Un cluster k-means vide part à l’origine. La mise à jour des centroïdes part de zéros et saute les clusters sans lignes (
kmeans.rs, lignes 137-152).KMeans(3)sur[5, 5, 9, 9]donne les centroïdes[5.0, 9.0, 0.0], etpredict([1.0])renvoie le cluster vide ; scikit-learn donne[5.0, 9.0, 9.0]avec un avertissement. - La silhouette d’un singleton vaut 1.
silhouette_score_exactdansix-voicingsfixea = 0pour une ligne seule dans son cluster, doncs = 1, là où Rousseeuw et scikit-learn utilisent 0 (lib.rs, lignes 664-678) : 0.9296 contre 0.5963 sur[0, 1 | 10]. - Les imports des tutoriels ne compilent pas.
use ix_unsupervised::{KMeans, Clusterer};échoue avecE0432, parce que la crate n’exporte que des modules (lib.rs, lignes 5-14). La même forme se trouve dansdocs/unsupervised-learning/kmeans.md(lignes 60 et 91),dbscan.md(ligne 50),pca.md(lignes 63 et 101),docs/use-cases/fraud-detection.md(ligne 43),gis-spatial-analysis.md(lignes 79, 375, 427),docs/foundations/rust-for-ml.md(ligne 141), et dans leurs versions françaises. Le doctest du cours vérifie la première. - Un seul départ pour k-means.
KMeans::fitlance un seul départ k-means++, et l’outil MCPix_kmeansfixe la graine à 42 (handlers.rs, ligne 322). Sur les jobs, les graines 0 à 9 donnent des inerties de 411.23 à 618.02 ; pourk = 6, la graine 42 donne 223.366 là où le meilleur de 50 départs de scikit-learn donne 214.040, avec une silhouette de 0.3646 contre 0.4400. - La règle des voicings ne compare pas.
ix_voicings::clustergardek = 5dès que sa silhouette atteint 0.15, et n’essaiek = 3qu’en dessous (lib.rs, lignes 770-776). Sur les jobs, elle gardek = 5(0.4350) plutôt quek = 3(0.4997). Un choix de conception plutôt qu’une erreur, mais le commentaire de documentation présente le seuil comme celui d’« acceptation du partitionnement » (accepting the clustering), pas comme celui du choix dek. - Effondrement du mélange gaussien.
GMMimpose à chaque variance un plancher de1e-6(gmm.rs, lignes 170-171) et lance un seul départ aléatoire. Sur les durées en secondes entières, des composantes s’effondrent surcomplete_s, et la log-vraisemblance dépend de la graine, de −102.666 à 552.879 ; le meilleur de 10 départs de scikit-learn est −102.667. Comparer les log-vraisemblances d’IX choisit le modèle le plus effondré.
Des différences bonnes à connaître, que je ne compte pas comme des erreurs :
LogisticRegressionn’a ni régularisation ni test d’arrêt : sur des données séparables, ses poids grandissent avec le nombre d’itérations (leçon 3).ix_optimize::gradient::SGDest une descente de gradient sur le lot complet :minimizelui passe le gradient sur toutes les lignes.DBSCANétiquette le bruit0et les clusters à partir de1, comme le dit sa documentation ; un code écrit pour le-1de scikit-learn fond le bruit dans un cluster.ix_io::csv_io::read_csvtransforme les champs texte enNaNsans erreur.train_test_splitmélange toujours, et prend les étiquettes de classification enf64; il n’y a pas d’option stratifiée ni chronologique.
2026-09-15 — Leçons 5 à 8
Section intitulée « 2026-09-15 — Leçons 5 à 8 »- Quatre leçons d’un coup : composantes principales, ensembles, réseaux de neurones, optimisation. Le cours dépend désormais de sept crates IX,
ix-ensembleetix-nnayant été ajoutées au même commit épinglé, et exécute seize exemples en CI. - La vérification croisée a grandi avec elles. La leçon 5 est la première où scikit-learn peut vérifier IX directement et complètement —
PCAest déterministe, ne prend pas de graine, et a une convention de signe documentée — si bien que chaque nombre de cette leçon a trois sources indépendantes. - La leçon 6 a introduit un générateur xorshift 64 bits dans le code du cours. IX tire de
StdRng, que Python ne peut pas rejouer ; trois décalages et trois XOR se rejouent exactement, et la vérification numpy retombe sur les mêmes 48 lignes laissées de côté par le même bootstrap. Cela comble le manque dont se plaignait la note du 2026-09-14, pour les algorithmes que le cours écrit lui-même. - La leçon 7 est la première à utiliser les différences finies comme oracle plutôt que scikit-learn. Chaque affirmation sur ce que calcule
ix_nna été mesurée en bougeant un poids et en regardant la perte, pas déduite d’une lecture — ce qui s’est avéré compter, puisque deux des trois constats se trompent d’un facteur que personne ne devinerait à la lecture du code. - La leçon 8 n’a rien trouvé à redire aux règles de mise à jour d’
ix_optimize: SGD, Momentum et Adam suivent les versions à la main pas à pas et atterrissent sur les mêmes points. Le constat, là, porte sur le type de résultat deminimize, pas sur son arithmétique.
2026-09-15 — Là où IX diffère, suite
Section intitulée « 2026-09-15 — Là où IX diffère, suite »Dix autres endroits où la réponse d’IX, ou sa documentation, diffère du manuel ou de scikit-learn, chacun montré par du code compilé dans le cours. Avec les neuf du 2026-09-14, cela fait dix-neuf. Aucun n’a fait l’objet d’une issue IX.
- Le ratio de variance expliquée est normalisé sur les composantes retenues.
explained_variance_ratiodivise chaque valeur propre par la somme des valeurs propres retenues (pca.rs, lignes 46-57), si bien que les ratios somment à 1 pour toutn_componentset que le « proportion of total variance » du commentaire de documentation n’est vrai que si toutes les composantes sont gardées. Sur les jobs standardisés,PCA::new(2)rapporte[0.5635, 0.4365]là où la vérité, et scikit-learn, disent[0.3867, 0.2996], de somme 0.6863. La question habituelle « combien de composantes pour 90 % ? » ne peut pas lui être posée. - L’itération de puissance part d’un vecteur fixe et peut renvoyer l’axe mineur. Le départ est
(1, …, 1)/√n(pca.rs, ligne 107). Sur un nuage de six points étiré le long de(1, -1), de covariance[[2.4, -1.6], [-1.6, 2.4]], ce départ est le vecteur propre mineur, doncPCA::new(1)renvoie une variance de 0.8 le long de(1, 1)au lieu de 4.0 le long de(1, -1).PCA::new(2)fait pire : la déflation laisse une matrice qui envoie le départ sur zéro, le garde-founorm < 1e-15se déclenche, et le modèle finit avec deux fois la même composante et une variance de 0. La SVD de scikit-learn renvoie 4.0 et(0.7071, -0.7071). Un cas sur le fil du rasoir — il faut des variances exactement égales — mais rien dans la sortie ne dit dans quel cas on est. - Les variances sont inaccessibles.
PCA::explained_varianceest un champ privé, etexplained_variance_ratio()est normalisé comme au constat 10, si bien quesave_state()est le seul moyen de sortir les valeurs propres d’un modèle ajusté. - Une forêt aléatoire tire ses variables une fois par arbre, pas une fois par division. Le commentaire de documentation promet « random feature subsets (sqrt(n_features) features per split) » ; le code les tire avant la croissance de l’arbre et construit tout l’arbre à partir de ces colonnes (
random_forest.rs, lignes 68-84). Au défaut de trois variables sur cinq, les deux s’accordent, atteignant toutes deux 0.9211 sur les jobs de test ; àmax_features = 1, la version à la main, qui tire à chaque division, atteint 0.9474 et IX 0.7632, parce que cinquante arbres unidimensionnels ne peuvent pas combiner de variables. - Une égalité dans le vote de la forêt revient au plus grand indice de classe.
predictutilisemax_by, qui garde le dernier maximum (random_forest.rs, ligne 97). Deux souches avec la graine 6 donnent des probabilités[0.500, 0.500], et IX répond la classe 1 là où scikit-learn etnp.argmaxrépondent 0. Même forme que le constat 3 surKNN::predict. Dense::backwarddivise deux fois par la taille du lot.grad_outputporte déjà le1/nquemse_gradienty a mis, etbackwarddivise encore parn(layer.rs, lignes 40-52). Mesuré contre le gradient par différences centrées demse_losssur les 65 builds, le rapport vaut exactement 65.0000. L’entraînement converge quand même, puisque chaque couche divise par le mêmen, mais le taux d’apprentissage que vous passez est le taux d’apprentissage divisé par la taille du lot.mse_gradientn’est pas le gradient demse_lossau-delà d’une colonne de sortie.mse_lossmoyenne sur les lignes et les colonnes,mse_gradientne divise que par les lignes (loss.rs, lignes 6-16). Mesuré de une à quatre colonnes, le rapport vaut 1, 2, 3, 4. Combiné au constat 15, une couche à deux sorties sur 65 lignes bouge du vrai gradient divisé par 32.5.Dense::newne prend pas de graine. Il tire du générateur du fil d’exécution (layer.rs, ligne 27), si bien que deux couches construites dans la même exécution diffèrent déjà et qu’aucun résultat deSequentialne peut être reproduit ni testé contre les régressions. Tous les autres algorithmes aléatoires d’IX en prennent une —KMeans,RandomForest,Dropout,transformer::FeedForward. Les champsweightsetbiassont publics, c’est la sortie de secours et c’est ce que fait le cours.- Un
Sequentialde couchesDenseest une seule application affine.Denseest le seul type qui implémente le traitLayer:ix-nnn’a aucune couche d’activation queSequential::pushpourrait accepter entre deux applications affines. Sur le ou exclusif, deux couchesDenseconvergent vers une perte de 0.25 — la variance des cibles, le mieux qu’une constante puisse faire — et prédisent 0.5 aux quatre coins, tandis que le réseau à la main de même taille, avec une sigmoïde entre ses couches, atteint 0. Après 50 000 époques, la pile satisfait encoref(0,0) + f(1,1) - f(0,1) - f(1,0) = 0au dernier bit : elle n’est pas sous-entraînée, elle ne peut pas représenter la fonction. Les vraies non-linéarités du crate vivent danstransformer::gelu, à l’intérieur deFeedForward, qui travaille surArray3et n’est pas unLayer. minimizerenvoie le point de départ pour une exécution qui a divergé. Elle retient le meilleur point traversé et renvoie celui-là (gradient.rs, lignes 124-165). La descente simple au taux 0.01 sur Rosenbrock part en NaN dès ses premiers pas ; le résultat annoncebest f 24.200000, qui estfau point fourni par l’appelant, avecconverged: false— le même drapeau qu’obtient une exécution parfaitement bonne qui a simplement épuisé ses itérations. Nibest_valueniconvergedne sépare les deux.
Des différences bonnes à connaître, que je ne compte pas comme des erreurs :
RegressionStumpmet la moyenne des résidus dans chaque feuille, là où la recette de Friedman et scikit-learn emploient un pas de Newton. C’est une simplification défendable, et la version à la main la copie pour que les deux soient comparables ; avec elle, IX et la version à la main s’accordent sur chacune des 38 lignes de test à 1, 5, 10, 25 et 50 tours.GradientBoostedClassifiern’a ni arrêt anticipé ni score sur un jeu réservé. Sur les jobs il culmine à 0.9474 après cinq tours et se stabilise à 0.9211 dès vingt-cinq, et rien dans l’API ne le mesure.ix-ensemblen’a pas de score out-of-bag, que le bagging offre gratuitement et que scikit-learn expose paroob_score=True.ClosureObjectivene redéfinit jamaisObjectiveFunction::gradient, si bien qu’envelopper une closure achète silencieusement un gradient mesuré au prix de deux évaluations supplémentaires par coordonnée et par pas — 1001 évaluations contre 201 pour 200 pas d’Adam en deux dimensions. Sur un objectif lisse, cela ne coûte rien en précision : les deux exécutions ont fini à1.061e-16.
2026-09-15 — Ce qui a concordé
Section intitulée « 2026-09-15 — Ce qui a concordé »Cela mérite d’être consigné à côté des constats, car les leçons ont surtout trouvé l’inverse jusqu’ici :
- Les SGD, Momentum et Adam d’
ix_optimizesuivent exactement les versions à la main — mêmes nombres de pas (5000, 4129, 2822), mêmes points finaux, même premier pas depuis le même gradient. GradientBoostedClassifiersuit la version à la main sur chaque ligne de test à chaque nombre de tours, et numpy reproduit les mêmes exactitudes, les mêmes log-priors lissés et les mêmes souches du premier tour.- Les cinq variances de
PCAcorrespondent à celles de Jacobi à1.28e-10près et à celles de scikit-learn aux chiffres affichés ; seuls le ratio et le vecteur de départ fixe sont en cause.
2026-09-15 — La carte d’API
Section intitulée « 2026-09-15 — La carte d’API »- Le plan promet une leçon par famille d’algorithmes, ce qui laissera encore l’essentiel du workspace intouché.
scripts/sync-ix-api-map.mjscomble le manque par l’autre bout : un seulgit grepsur le commit épinglé, et une page générée par locale listant chaque déclarationpubsouscrates/*/src/, crate par crate et module par module. 80 crates, 3398 déclarations, dont 2307 fonctions. - Elle est générée, jamais écrite à la main, et
npm run sync:ix-api-mapla reconstruit — elle ne peut donc pas s’écarter du pin, ce qui est d’ordinaire ce qui pourrit une page de ce genre. La révision est lue dans leCargo.tomldu cours plutôt qu’écrite deux fois. - Elle compte ce que dit la source, pas ce qu’un appelant peut atteindre : un
pubdans un module privé est listé et reste invisible depuis l’extérieur du crate. Le constat 12 — un champ privé qui rend le ratio inutilisable — est exactement le genre de chose que la page ne peut pas dire, et que les leçons peuvent.
2026-09-16 — Un zéro négatif sous macOS
Section intitulée « 2026-09-16 — Un zéro négatif sous macOS »- La première exécution CI du code des leçons 5 à 8 était rouge sur
macos-latestseulement, dansl07_networks: le réseau du ou exclusif affichaitpredictions [-0.0000, 1.0000, 1.0000, -0.0000]là où Windows et Linux affichent0.0000à ces deux coins. Exécution 35039180659 ; les trois autres jobs ont réussi, ainsi que les quinze autres exemples. - Aux deux coins nuls, le réseau atterrit sous la quatrième décimale, et seul le signe diffère — les mêmes additions prises dans un autre ordre sur ARM.
-0.0et0.0sont le même nombre et se comparent égaux : le signe n’est pas un résultat, et aucune affirmation des leçons ne change. fmt_vecexiste pour que « les sorties ne dépendent pas de la façon dont chaque OS imprime les derniers chiffres », ce qui en fait l’endroit du correctif : tout ce qui s’arrondit à zéro affiche désormais0.0000, jamais-0.0000, et tout autre signe survit. Un test unitaire fixe les deux moitiés. Aucun fichierexpected/n’a changé, donc aucune leçon n’a eu à être recitée.- La note du 2026-09-14 — des décimales fixes, donc des sorties identiques sur les trois systèmes — avait raison sur le remède et lui manquait un cas. Des décimales fixes ne tranchent pas le signe d’un zéro, et un cours écrit sur une seule machine ne peut pas l’apprendre.
2026-09-24 — Cinq réducteurs, trois entrées
Section intitulée « 2026-09-24 — Cinq réducteurs, trois entrées »l09_reducers.rsa tourné sous Windows avec le commit IX épinglé : un carré pour la MDS, deux anneaux pour la PCA à noyau, et les données CI de 186 jobs pour la NMF et la LDA (les douze premières lignes standardisées pour t-SNE). La MDS écrite à la main et IX retrouvent les six distances du carré à1e-10près.- Sur les anneaux, l’écart du premier axe linéaire est
0.0000. Celui du premier axe RBF est lui aussi0.0000; le quatrième atteint0.5798. L’hypothèse exploratoire sur le premier axe est donc réfutée. Le signe et la rotation des vecteurs propres étant arbitraires, la comparaison prend les écarts absolus entre moyennes sans en déduire un score de classification. - La NMF de rang deux reconstruit la matrice brute des durées avec une erreur quadratique moyenne de
0.505secondes carrées par cellule et refuse les entrées standardisées négatives. La LDA produit deux axes finis pour trois étiquettes de système d’exploitation, sans prétention de classification hors échantillon. t-SNE avec graine donne un arrangement fini de taille12 × 2, maistransformrenvoie exactement le même arrangement avec une entrée sans rapport de taille1 × 1. - Le contrôle indépendant numpy/scikit-learn 1.8.0 confirme les distances du carré, le résultat des axes des anneaux, la reconstruction NMF finie, les deux axes LDA et l’embedding t-SNE fini. Ce sont des contrôles d’invariants, pas une égalité des facteurs NMF ni des coordonnées t-SNE entre implémentations. Les hypothèses n’ont pas été consignées avant l’exécution : il s’agit de résultats exploratoires, non de confirmations pré-enregistrées. La CI sur trois systèmes pour la leçon 9 reste à faire.
À vérifier
Section intitulée « À vérifier »- L’outil
ix_ml_pipelinede bout en bout : l’ordre de mise à l’échelle du constat 1, l’inférence de tâche du constat 2 sur un fichier CSV, et l’erreurAll rows contain NaN valuespour un fichier avec une colonne texte. Les trois sont lus dans le code, pas exécutés. LinearRegression::fitsur deux caractéristiques identiques : siix_math::linalg::inverserenvoie une erreur et quefitpanique, ou s’il renvoie une mauvaise réponse.- Un cluster k-means qui se vide au milieu d’une exécution réelle, plutôt qu’au départ comme dans le constat 4.
ix_voicings::clustersur les voicings de GA : l’export demande leFretboardVoicingsCLIde GA, que le cours ne construit pas.- La cause du build de 48 secondes de
95a3830. - Si les constats 1 à 9 sont déjà connus en amont : je n’ai pas cherché dans les issues d’IX.
- Les exemples sur les runners Linux ARM : la CI couvre seulement
ubuntu-latest(x64),windows-latestetmacos-latest(ARM). - Si
PCArenvoie parfois des variances non triées sur de vraies données, plutôt que sur le nuage construit du constat 11. - Si un
Sequentialplus profond que deux couchesDenseest utilisé quelque part dans IX, où le constat 18 mordrait. ix-autograd: son ruban est le crate qui devrait rendre le constat 15 inutile, et la leçon 12 le mesurera.- Si les constats 10 à 19 sont déjà connus en amont : je n’ai toujours pas cherché dans les issues d’IX.
- La carte d’API compte les déclarations
pub, pas celles qui sont atteignables ; l’écart entre les deux nombres n’est pas mesuré. - Les valeurs affichées par un
println!("{:.6}")direct plutôt que parfmt_vec— l’intercept -0.000000de la leçon 8 en est une — portent le même risque de zéro signé et ne sont pas normalisées. Celle-là concordait sur les trois systèmes dans l’exécution 35039180659 ; les autres n’ont pas été recensées. - Rejouer les sorties numériques de la leçon 9 sur les CI Linux et macOS ; vérifier que l’erreur NMF et l’écart du quatrième axe RBF s’arrondissent de la même façon. Mesurer séparément l’évolution du temps de t-SNE avant d’en chiffrer le coût.