perf: compute only top-rank principal directions in PCAEM#23
Open
jamgochiana wants to merge 2 commits into
Open
perf: compute only top-rank principal directions in PCAEM#23jamgochiana wants to merge 2 commits into
jamgochiana wants to merge 2 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PCAEM.fit previously called MultivariateStats' PCA with maxoutdim=rank, which only truncates an already-computed full decomposition: a full eigen of the d x d covariance when d < n, or a full svd of the centered data when d >= n. When rank is much smaller than min(d, n) this wastes almost all the work. PCAEM only needs the projection/loadings P and the mean mu from PCA; everything downstream (reduced data, reconstruction, and the diagonal noise D) derives from those. This adds a _truncated_pca helper that eigendecomposes only whichever Gram matrix is smaller: the d x d covariance when d <= n, or the n x n Gram matrix Z'Z when d > n (recovering feature-space loadings via Z * W). Picking the smaller matrix by the d <= n branch is essential; eigendecomposing the larger one is catastrophically slow. Small problems fall back to a dense eigen and truncate, since Arpack's iterative eigs has high overhead for tiny/low-rank cases and requires nev < size - 1. The threshold is min(d, n) <= 100 or rank >= min(d, n) - 1. Adds an Arpack dependency (eigs). Verified equivalent to the full-PCA output at the level PCAEM uses: the top-rank reconstruction subspace (P*P') and the reconstruction-error diagonal D match to ~1e-8. Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Closes #18
Problem
PCAEM.fitcalledpca_fit(PCA, x; maxoutdim=rank).maxoutdimonly truncates an already-computed full decomposition; it does not make MultivariateStats compute fewer components. Ford < nPCA does a fulleigenof thed x dcovariance, and ford >= na fullsvdof the centered data. Whenrankis far smaller thanmin(d, n), almost all of that work is wasted.Approach
PCAEM.fitonly needs two things from PCA: the loadingsP(d x rank) and the meanmu. Everything downstream (reduced data, reconstruction, and the diagonal noiseD) derives from those.The new
_truncated_pcahelper eigendecomposes only whichever Gram matrix is smaller:d <= nbranch: eigendecompose thed x dcovarianceSymmetric((Z Z') / (n-1))and take the top-rankeigenvectors asP.d > nbranch: eigendecompose the smallern x nGram matrixSymmetric((Z' Z) / (n-1)), then recover feature-space loadings viaU = Z Wand normalize columns.Choosing the smaller matrix by the
d <= nbranch is essential: eigendecomposing the larger Gram matrix is catastrophically slow (e.g.eigs(ZZ')on d=20000, n=1000 is ~12x slower than baseline).Small-dimension dense fallback
When the smaller dimension is small, Arpack's iterative
eigshas high overhead (and can fail to converge), andeigsrequiresnev < size - 1. The helper falls back to a denseeigen+ truncation whenmin(d, n) <= 100orrank >= min(d, n) - 1. The existing PCAEM tests (d=20, n=1000) and edge cases hit this fallback, which is correct and expected. The existingrank <= n_featuresArgumentErrorcheck is preserved.New dependency
Adds
Arpack(eigs) to[deps]and[compat].Equivalence tests
Added a "Truncated PCA equivalence" testset in
test/test_fitting.jlverifying, againstpca_fit(PCA, x; maxoutdim=rank), that:mumatches (~1e-10).P*P'matchesP_full*P_full'(sign/rotation-invariant) to ~1e-8.Dmatches to ~1e-8.These cover both branches and both the dense-fallback path (d=20/n=1000, d=200/n=30) and the iterative
eigspath (d=150/n=400, d=400/n=150). Loadings are only defined up to per-column sign / rotation, so only sign/rotation-invariant quantities are asserted. The existing PCAEM testset (LRDMvNormal components, correct rank, finite logpdf, valid weights) and edge-case tests remain unchanged and pass.Results
Full suite passes: LRDMvNormal 53/53, Fitting Methods 104/104, Prediction and Marginal Functions 24/24. (Unrelated GaussianMixtures "Too low occupancy" warnings are expected.) Sanity benchmark on d=5000, n=800, rank=5 showed ~9x speedup vs the full PCA.
🤖 Generated with Claude Code