maroon: PCA and k-means over Lagoon arrays (Hoon) - #90
Merged
Merged
Conversation
Step 3 of #86's foundation list, stacked on #88 (whose +svd backs +pca) and #87 (whose axis arms back everything else). Hoon only, no jet hints. This is the classical-ML line; the tinygrad autodiff port in /lib/tinygrad is a separate track (#85) and is untouched. /lib/maroon is new. A dataset is an n x d %i754 ray, one row per sample; labels and indices are %uint bloq-6 rays, exactly what +argmin-dim returns, so the pieces compose without reshaping. Arms are written in terms of whole-array Lagoon operations (+cdist-sq, +argmin-dim, +mean-dim, +mmul, +broadcast-to) rather than per-element loops, so the jets those arms grow will carry the models with them; +update, which gathers rows per cluster, is the exception. +pca / +pca-transform principal components from the SVD of the CENTRED data rather than an eigendecomposition of the covariance, which would square the condition number. Saloon's one-sided Jacobi already sorts descending, which is the order PCA wants. +kmeans Lloyd's algorithm from given centroids: deterministic, stopping at the exact fixed point of the assignment (not a tolerance) and reporting the iteration count. +kmeans-pp k-means++ seeding via /lib/i754rand's alias table, threading the generator explicitly so a seed reproduces its centroids. +kmeans-fit is the two. +assign +update the two halves of a Lloyd step; an empty cluster keeps its previous centroid. +cov +center +col-mean +inertia +row +set-row Splitting the deterministic Lloyd iteration from the random seeding is what makes this testable: the iteration is asserted exactly, the seeding only for the properties that hold whatever it draws. Tests: 40 arms. The exact cases compare whole rays -- cluster means that are exact halves, a dataset whose first principal component is e1 exactly (its centred columns are already orthogonal, so Jacobi performs no rotation), an exactly representable covariance at ddof=0. The approximate ones compare against scikit-learn within a tolerance; the explained VARIANCE is always approximate even on exact data, since it returns through Saloon's Newton-iteration square root (a true variance of 1 comes back as 0.9999999999999998). maroon/tools/ml_check.py re-derives all of it (23 checks) with exact rational arithmetic plus numpy/sklearn references, and corrected the +pca doccord example I first wrote. Also symlinks the libs the desk was missing (complex, unum, rand, i754rand, sur/rand), per the convention that everything external to maroon is a symlink. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TgBnKsUPYzkoPZonjePnaq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 3 of the foundation work list in #86: the first classical models. Hoon only, no jet hints.
Stacked on #88 (base
sigilante/saloon-linalg), which is itself on #87 —+pcauses #88's+svd, and everything else uses #87's axis arms. Review those first; this diff is only themaroon/files.This is the classical-ML line. The 2024 tinygrad autodiff port in
/lib/tinygradis a separate track (#85) and is untouched here./lib/maroonA dataset is an
n x d%i754ray, one row per sample. Labels and indices are%uintbloq-6 rays — exactly what+argmin-dimreturns — so the pieces compose without reshaping.+pca,+pca-transform+kmeans+kmeans-pp,+kmeans-fit+assign,+update+cov,+center,+col-mean,+inertia+row,+set-rowEverything is written in terms of whole-array Lagoon operations (
+cdist-sq,+argmin-dim,+mean-dim,+mmul,+broadcast-to) rather than per-element loops, so the jets those arms eventually grow carry the models with them.+updateis the one exception — it gathers rows per cluster.Three decisions worth flagging:
Xc^T*Xcsquares the condition number, and Saloon's one-sided Jacobi already returns singular values in descending order, which is the order PCA wants.+covis still there for callers who want the matrix itself.+kmeanstakes its starting centroids and is deterministic;+kmeans-ppis the random part and threads an/lib/randgenerator explicitly. That split is what makes this testable — the iteration is asserted exactly, the seeding only for properties that hold whatever it draws.+kmeansstops at the exact fixed point of the assignment rather than a tolerance, and reports the iteration count so a caller can tell convergence from exhaustion. An empty cluster keeps its previous centroid.Tests: 40 arms
maroon-pca.hoon(19) andmaroon-kmeans.hoon(21).Exact, compared as whole rays: column means and centring; a covariance that is exactly representable at
ddof=0; a dataset on the x-axis whose first principal component ise1exactly (its centred columns are already orthogonal, so Jacobi performs no rotation at all); cluster means that are exact halves;+assigntie-breaking to the lower centroid; an empty cluster keeping its centroid; inertia;maxit=0; and the crash paths.Approximate, against scikit-learn: the rotated 45-degree case, its explained variance (
13.333333333333336), and two-component extraction. Note the explained variance is approximate even on exact data, because it comes back through Saloon's Newton-iteration square root — a true variance of 1 returns as0.9999999999999998, so that expectation is a tolerance rather than bits.The seeding tests assert what must hold regardless of the draw: same seed → same centroids, seeds are distinct samples drawn from the data, and
+kmeans-fitrecovers a well-separated partition (compared up to label permutation, since cluster numbering is arbitrary).maroon/tools/ml_check.pyre-derives all of it — 23 checks, 0 failures — mirroring each operation infractions.Fractionto prove the exact cases never round, with numpy/sklearn for the rest. It also corrected the+pcadoccord example I first wrote.Verification
Fresh fakeship at hoon-135/zuse-408 on a vere built from urbit/vere#1057 (plus the
@rqfix from #89),%numdesk carrying lagoon, saloon, maroon and the libmath/librand deps:+40 OK, no change to the failures: the 4 are the pre-existing
saloon-unumposit transcendentals, which fail on every runtime including vere 4.6.Housekeeping
The desk was missing symlinks for libs it pulls in transitively (
complex,unum,rand,i754rand,sur/rand) — added, per the convention that everything external to maroon is a symlink. All 14 now resolve.Not in this PR
Jets; GMM, spectral clustering and kNN; and regression, which wants QR first. Per #86 the next step is Cholesky/QR plus linear and ridge regression.
Refs #86.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TgBnKsUPYzkoPZonjePnaq