perf(splines): drop the n_train square projector from tprs transform - #25
Open
ChrisW09 wants to merge 1 commit into
Open
perf(splines): drop the n_train square projector from tprs transform#25ChrisW09 wants to merge 1 commit into
ChrisW09 wants to merge 1 commit into
Conversation
``ThinPlateSplineTransformer.transform`` rebuilt the null-space projector
``P = I - Z (Z'Z)^-1 Z'`` in full on every call. ``Z.shape[0]`` is the
training-set size, so transforming a handful of rows against a moderate fit
allocated an n_train x n_train float64 matrix -- 250 MB and 42 ms to transform
ten rows after fitting on 8000 -- and redid the pseudo-inverse each time.
The dense projector is never needed: distributing the product gives
``K_new @ P == K_new - (K_new @ Z) (Z'Z)^-1 Z'``. Cache ``(Z'Z)^-1`` at fit
time and use that form instead.
Numerically identical to the previous formulation (max abs difference 1.4e-14
over a 300-row fit and 37 out-of-range query points).
n=2000: 6.3 ms -> 0.41 ms, peak 0.64 MB
n=8000: 42.4 ms -> 0.72 ms, peak 2.56 MB (was 256.7 MB at n=4000)
This addresses the transform side only. The O(n^2) cdist and O(n^3) eigen-
decomposition in ``fit`` are inherent to the exact TPS construction and are
left for a separate decision.
Refs #13
Co-Authored-By: Claude Opus 5 (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.
Addresses the transform-side half of #13. Deliberately does not close it — see below.
Problem
ThinPlateSplineTransformer.transformrebuilt the null-space projector on every call:Z.shape[0]is the training size, so the cost of transforming a few rows scaledquadratically with the fit — 250 MB and 42 ms to transform ten rows against an 8k-row fit —
and the
pinvwas recomputed each time.Fix
The dense projector is never needed. Since
P = I - Z (Z'Z)^-1 Z':(Z'Z)^-1is now cached at fit time as_ztz_inv_.Result
Numerically identical — max absolute difference
1.4e-14against the explicit-projectorformulation over a 300-row fit and 37 query points including out-of-range ones.
Scope — why this does not close #13
#13 also covers the
fitside, where a densen x ncdistand a fulleighmake 8k rowstake ~40 s and 50k rows get OOM-killed. That cost is inherent to the exact TPS
construction, so fixing it means either documenting a supported ceiling, raising above a
threshold, or implementing a knot-subsampled low-rank variant (Wood's thin-plate regression
splines). Each is a product decision rather than a bug fix, and adding a size guard would
introduce a new user-visible failure mode.
I have left #13 open for that decision and marked this
Refs, notCloses.Tests
Three added to
tests/test_thinplate_transformer.py:test_tprs_transform_matches_explicit_projector— pins numerical equivalence against aliteral
np.eye-based projector, so the algebraic rewrite can't silently drifttest_tprs_transform_does_not_allocate_a_train_sized_matrix—tracemallocpeak must stayunder a quarter of a dense
n_train²float64 array; this fails on the old codetest_tprs_caches_the_pseudo_inverseFull suite: 483 passed, 9 xfailed.
ruff checkclean on changed files; pyright unchanged atits pre-existing 71 errors.
🤖 Generated with Claude Code