Repository navigation
Conversation
pyRATS called np.random.seed(42) at seven places, so every fit reset the caller's global random state (after fit_transform, np.random was left freshly seeded with 42). The draws now come from local numpy.random.RandomState generators seeded with the same value: every fit draws the same numbers in the same order as before, the results are unchanged bit for bit, and NumPy's global state is left alone. - lpca: the ARPACK start vector of every local SVD, reseeded per point as before. - kpca: the seed is now an argument of the function (still 42). - The noise seeds of the intermediate views, the shuffle that picks the far-off points (n_repel > 0) and the start vector of the spectral initialization share one generator per fit, reseeded where the global one was, so the shuffle continues the same stream as before. - Tear coloring: the start vector of eigsh in every component. - The two reseeds with no draw after them (compute_final_embedding, spectrum_of_laplacian_from_neighbors) are removed. - Param's noise draws use a local generator with the same seed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RATS(random_state=42) exposes the seed that pyRATS fixed internally. The default, 42, is the value used so far, so results do not change unless a different seed is passed. Following scikit-learn, random_state accepts an int, None (a new seed from NumPy's global generator at every fit) or a RandomState instance; a numpy.random.Generator is accepted too. The value is resolved to one int per fit, which seeds every random draw of the fit: the start vectors of the local PCA (ARPACK), kernel PCA, the spectral initialization and the tear coloring, k-means when n_forced_clusters > 1, and the choice of far-off points when n_repel > 0. The seed reaches the loky workers of the local PCA as an argument, so results do not depend on n_jobs. compute_color_of_pts_on_tear reuses the seed of the last fit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same seed gives the same embedding and another seed another one; the default is 42; a RandomState or Generator supplies one seed per fit, and None draws it from NumPy's global generator; the seed reaches the worker processes of the local PCA (n_jobs=1 and n_jobs=2 agree bit for bit); the tear coloring uses the seed of the fit; NumPy's global random state is untouched by a fit; invalid values raise ValueError. The data are generated in the file (no optional dependencies) and small: the 13 tests take about 17 s on one core. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci.yml ran only tests/test_e2e.py; this adds a step for tests/test_random_state.py (about 20 s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Explains random_state and its default, says that pyRATS leaves NumPy's global random state alone, and shows how to measure how much an embedding moves between seeds after a rigid alignment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RATS now stores, as Utilde_t, the incidence (views x points) used in the last step of the alignment: Utilde restricted by the tear. The final embedding of each point is the mean of what these views give it. Utildeg is recomputed from the final embedding, one step later, so it cannot be used to rebuild the embedding of points near the tear. compute_final_embedding returns the incidence only with return_incidence=True, so its existing callers are unaffected. Nothing else changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fit(X) fits the model and returns self; the embedding is stored in embedding_, which fit_transform now sets too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
transform(X) gives every point the coordinates that the fitted views give it: the local (kernel) PCA of each view it belongs to, with the rotation and translation found by the alignment, averaged over its views as the fit averages its own points. The fitted embedding does not change, and training points get their own coordinates. - A new point belongs to the views of the training points that would have it among their n_neighbors nearest neighbors (distance below their k-th neighbor distance), the rule by which the fit assigns its own points to views. - With tearing, only the views that contain its nearest training point are kept, so a point near the tear lands on that neighbor's side instead of between the two sides. - A point that no training point would count as a neighbor is embedded with the views of its nearest training point, and a warning says how many there were. - return_residual=True also returns each point's mean distance to the affine subspaces of its views, which flags points off the manifold (linear PCA only). Param gains eval_new_ and residual_new_, which map points that are not in the training data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fit returns self and stores the same embedding as fit_transform; transform reproduces the embedding of the training points, with and without tearing; held-out points land among their neighbors, and near a tear none falls between the two sides (the test checks that averaging the neighbors' embeddings would put some there); held-out points land where a refit that includes them puts them; points far from the data warn; the residual flags points pushed off the manifold; input checks; kernel PCA. The data are small and generated in the files: the 10 tests take about 16 s on one core. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a step for tests/test_transform.py (about 20 s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shows fit followed by transform, the optional residual, and what transform does with training points and with points far from the data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
During my visit to Devika Narain's lab I needed to place new points in an embedding fitted once, without refitting it.
This PR depends on #12 (
random_state). Its branch sits on top of that one, and its tests userandom_state. Until #12 is merged, the diff here also shows its commits; the changes of this PR are the last six commits.fitandtransformraisedNotImplementedError. This PR implements both:fit(X)fits the model and returnsself. The embedding is stored inembedding_, whichfit_transformnow sets too.transform(X, return_residual=False)embeds new points with the local views of the fitted model, without changing the embedding.How
transformplaces a pointA RATS embedding is an atlas. Each view
mis a local (kernel) PCAφ_m, and the alignment gives it a rotationT_mand a translationv_m. The embedding of a training point is the mean of what its views give it,y_i = mean over the views m of i of (φ_m(x_i) T_m + v_m): the least-squares solution of the last alignment step.transformapplies the same formula to a new point. Three rules decide which views it uses:n_neighborsnearest neighbors (closer than their ownn_neighbors-th neighbor). This is the rule by which the fit assigns its points to views; applied to the training points, it gives back their views.return_residual=Truealso returns each point's mean distance to the affine subspaces of its views. Large values flag points off the fitted manifold (linear PCA only).A fix that torn fits need
The final embedding comes from the incidence used in the last alignment step, but
Utildegis recomputed afterwards from the final embedding. With tearing, the two differ near the tear: rebuilding the embedding of a tornsmall_spherewithholefit fromUtildegmisses 27 of its 1 986 points, by up to 2.3 typical neighbor distances in the embedding. The fit now keeps the incidence of the last step asUtilde_t, which rebuilds all of them (to 2e-14).compute_final_embeddingreturns it only withreturn_incidence=True, so its existing callers, such asexamples/cut_and_paste_app.py, are unaffected.Tests and verification
tests/test_transform.py: 10 tests, about 16 s on one core, with small data generated in the files. They check that:fitreturnsselfand stores the embedding offit_transform;transformreproduces the embedding of the training points, with and without tearing (to 1e-10);A CI step runs them.
The fit does not change. I compared this branch with the branch of Add a random_state parameter to RATS (default 42: results unchanged) #12, bit for bit (
np.array_equal), on the 56 configurations of the full regression suite (tests/scripts/generate_snapshots.py), on three fits with the default constructor on 5 000 points with several cores, and on nine small configurations that reach every random draw (kernel PCA, the spectral initialization,n_forced_clusters=2,n_repel > 0with one and with two clusters,min_cluster_size=1, tearing). In all 68 comparisons,y, the tear colors,Utilde,C,n_Utilde_Utilde,Utildegandcwere identical, and two repeated runs of the base branch were identical to each other. Both branches ran in the same environment as in Add a random_state parameter to RATS (default 42: results unchanged) #12.transformreproduces the embedding of every one of those fits. Applied to the training data of each fit,transformreturned the embedding offit_transform, with a largest error of 2.5e-12 in the 59 large fits (onbarbell) and of 1.3e-14 in the nine small ones, including kernel PCA, tearing and forced clusters.Example: points near a tear
I fitted the Klein bottle of the README (2 000 points, with tearing) without 10% of its points, and then placed those 193 points two ways:
transformplaced all of them next to their neighbors: at most 2.8 typical neighbor distances from the nearest one.The README has a short section with the usage.
Not in this PR
inverse_transform(with linear PCA it could use the same views in reverse) and a residual for kernel PCA.Prepared with the help of Claude Code; reviewed and tested by me.