Skip to content

VFF implementation - #2

Closed
thomaspinder wants to merge 61 commits into
pymc-devs:mainfrom
thomaspinder:vff-implementation
Closed

VFF implementation#2
thomaspinder wants to merge 61 commits into
pymc-devs:mainfrom
thomaspinder:vff-implementation

Conversation

@thomaspinder

Copy link
Copy Markdown
Contributor

An implementation of Variational Fourier Features for Gaussian Processes. The implementation is based upon a fork of the authors' original code here. The main new functionality lies within the kernel's inducing fourier points, whilst the remaining code is predominantly wiring modules together and ensuring back compatibility e.g., base_conditional being connected to a whitened and unwhitened form.

On documentation, I have run a notebook locally that tests for equality in this implementation and the original authors' code. I did not commit though as it muddies the dependencies. I have extended the demo notebook to include the VFF approximation, and also to quantify the distance from each approximation's posterior to the exact posterior using the Wasserstein-2 distance (closed form when you have MVNormals).

bwengals and others added 22 commits April 21, 2026 16:02
Bernoulli and Poisson are cross-checked against GPJax; StudentT and
NegativeBinomial against a self-contained numpy+scipy reference ELBO
(whitened predict by hand, scipy.integrate.quad for the variational
expectation, closed-form whitened KL), since GPJax does not ship
those likelihoods. Both patterns pin the full ELBO wiring at atol=1e-5.
…redict_marginal

The existing predict returns per-point marginals, which can't be used to
draw smooth function samples — neighboring points' uncertainty is
uncorrelated. predict_joint gives the full (N, N) posterior covariance,
and predict_f_samples Cholesky-transforms caller-supplied iid-normal
noise into draws from that joint. This is the foundation for
posterior-predictive use with non-Gaussian likelihoods: sample f, push
through the likelihood.

Renamed predict to predict_marginal across SVGP, VFE, Unapproximated
to make the pair symmetric and the semantics explicit. base_conditional
gains a full_cov flag.
…iag onto kernels

Deletes ~300 lines of unused JAX-based fitting code and its re-exports,
along with two empty test files. Replaces the kernel_diag method on each
GP model with a diag(X) method on each kernel, written as a direct
expression (ones for stationary/Gibbs, x for RandomWalk, delegated for
WarpedInput, composed for Sum/Product) rather than diagonal of the full
matrix. PyTensor's rewrite system can't derive this automatically because
it requires semantic knowledge of the kernel.
…mplement _eval

Each kernel previously repeated the Y-is-None branch and the
specify_assumptions(symmetric, positive_definite) wrap. That scaffolding
now lives once in Kernel.__call__; subclasses provide a pure _eval(X, Y)
that's always called with both arguments. Shrinks every kernel by several
lines and leaves one place to change the annotation policy.
These classes only support a Gaussian likelihood, so expose the noise
parameter directly and build the Gaussian object internally.
Unapproximated and VFE stored _X_train/_y_train attributes that were never
assigned; VFE.predict_marginal even read self._X_train without initializing
it. Make X_train/y_train required args on both predict_marginal methods.

tests/conftest.py declared X_1d/X_2d/rng_key fixtures and referenced a
make_params helper that never existed; the fixtures were shadowed by local
definitions in every test that used those names.
Both hardcoded input_dim=1, preventing their use in sum/product kernels
and with multi-column inputs. Accept any input_dim; enforce a single
active column via active_dims (defaulting to [0] when input_dim == 1).
Follows PyMC's pattern (pm.sample, pm.sample_prior_predictive): a single
compile_kwargs dict forwarded to pytensor.function, covering mode
(NUMBA/JAX/C), allow_input_downcast, on_unused_input, etc. Applied to
compile_training_step, compile_scipy_objective, compile_predict, and
greedy_variance_init.

compile_training_step merges any caller-provided updates with the
optimizer's updates (mirroring pymc.pytensorf.compile).
Covers POSITIVE propagation (Sqr/Pow/Mul/DimShuffle/Alloc/ExtractDiag),
the new POSITIVE_DEFINITE inference paths (AllocDiag of symbolic positive
vector, Mul of positive scalar × PSD, set_subtensor diagonal pattern,
PSD-through-transpose, X.T·M·X and X.T·Solve(M,X) quadratic forms), the
specify_assumptions(positive=True) signature, and end-to-end SLogDet
lowering (firing, non-firing, Cholesky-sharing, numeric correctness).

Test bodies use only pytensor APIs so they can be lifted into pytensor
when the rules are upstreamed; the only ptgp imports are the side-effect
registration and the POSITIVE key.
Two scale-unlock features for SVGP on big-N data, planned as independent
PRs: 1D-Matérn variational Fourier features via a new InducingVariables
subclass with a Woodbury solve_Kuu bridge, and an exact closed-form
variational expectation for Poisson with exp link.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Four findings addressed:

- PR 2 (Poisson): the original plan proposed adding an analytic VE that
  already exists in ptgp/likelihoods/poisson.py. Rescoped to hardening
  (end-to-end SVGP test, gradient correctness, graph-level regression
  assertion). Dropped the invlink-callable→link-string API change, which
  would have been a silent breaking change for users passing custom
  invlinks.

- PR 1 (VFF) domain validation: specified runtime checks for training
  and prediction, a default refusal to extrapolate, and an opt-in
  allow_extrapolation escape hatch with explicit tests.

- PR 1 (VFF) Kuu contract: split K_uu (dense) from structured_Kuu
  (optional typed DiagPlusLowRank). solve_Kuu is the only dispatch
  point, so a buggy consumer calling K_uu on a VFF object gets a slow
  but correct result, not a shape-incompatible structured tuple.

- Algorithmic-path tests: both PRs gain CI-asserted graph-introspection
  tests that verify the fast path actually runs in the compiled graph,
  rather than relying on wall-clock notebooks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bwengals

bwengals commented May 6, 2026

Copy link
Copy Markdown
Collaborator

I screwed something up and didnt mean to close this... will fix. Sorry about that!

@bwengals

bwengals commented May 6, 2026

Copy link
Copy Markdown
Collaborator

ok looks like I cant reopen because I force pushed to main and now the git history isnt shared. Working on opening a new PR from a new banch, and then cherry picking your commits to that so you dont have to worry about doing a rebase

@bwengals

bwengals commented May 6, 2026

Copy link
Copy Markdown
Collaborator

ok so I tried that, and it looks like it/I am making a huge mess. My fault for pushing straight to main... Will start using branches properly now. Would you mind opening a new PR or doing a rebase or whatevers easier?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants