Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Multi-Output Gaussian Processes — LMC, ICM, OILMM

Open In Colab

Setup

A vector-valued GP f:Rd→RPf : \mathbb{R}^d \to \mathbb{R}^P assigns to every finite collection of inputs X={xn}n=1NX = \{x_n\}_{n=1}^N a joint Gaussian over f(X)∈RN×Pf(X) \in \mathbb{R}^{N \times P}. Stacking the columns by vec(F)\mathrm{vec}(F) — observations of output 1 first, then output 2, etc. — the prior is

vec(F)∼N ⁣(0, K),K∈RPN×PN,\mathrm{vec}(F) \sim \mathcal{N}\!\bigl(0,\, \mathbf{K}\bigr),\qquad \mathbf{K} \in \mathbb{R}^{PN \times PN},

and the entire question of “what is a multi-output GP” reduces to: what structure does K\mathbf{K} have? A plain PN×PNPN \times PN Gram matrix is too expensive — O((PN)3)\mathcal{O}((PN)^3) to factor — and is unstructured: it doesn’t say how outputs are coupled. The three constructions here put successively more structure on K\mathbf{K}:

KernelStructure imposed on K\mathbf{K}Solve cost (Cholesky)When to use
LMCKernelK=∑q=1QBq⊗Kq\mathbf{K} = \sum_{q=1}^Q B_q \otimes K_q with Bq=wqwq⊤B_q = w_q w_q^\top rank-1O((PN)3)\mathcal{O}((PN)^3) generic; O(QN3)\mathcal{O}(QN^3) when latents tiedOutputs share some structure but each latent has its own kernel / lengthscale.
ICMKernelK=B⊗K\mathbf{K} = B \otimes K — a single Kronecker productO(N3+P3)\mathcal{O}(N^3 + P^3) via (B⊗K)−1=B−1⊗K−1(B \otimes K)^{-1} = B^{-1} \otimes K^{-1}Outputs are smooth versions of one underlying field.
OILMMKernelK=∑q(wqwq⊤)⊗Kq\mathbf{K} = \sum_q (w_q w_q^\top) \otimes K_q with W⊤W=IQW^\top W = I_QO(QN3)\mathcal{O}(QN^3) via projection to QQ independent scalar GPsMany outputs, few latents (P≫QP \gg Q).

pyrox.gp exposes all three as equinox.Module kernels whose cross_covariance_operator returns a lineax operator carrying the corresponding structure tag (is_kronecker, is_block_diagonal). Downstream gaussx solvers dispatch on those tags so the dense (PN)×(PN)(PN) \times (PN) matrix is never materialised internally.

This notebook walks through:

  1. LMC on two coupled outputs — share information across outputs to predict where one is missing.
  2. ICM as a single-Kronecker special case — show the structure tag pyrox emits so solvers can pick the fast path.
  3. OILMM project / back-project workflow — many outputs, few latents, QQ independent scalar GPs.

Setup

Python implementation: CPython
Python version       : 3.13.5
IPython version      : 9.10.0

jax       : 0.9.2
equinox   : 0.13.6
gaussx    : 0.0.10
pyrox     : 0.0.6
lineax    : 0.1.0
matplotlib: 3.10.8

Compiler    : GCC 11.2.0
OS          : Linux
Release     : 6.8.0-1044-azure
Machine     : x86_64
Processor   : x86_64
CPU cores   : 16
Architecture: 64bit

1. LMC — borrowing strength across outputs

Definition

The Linear Model of Coregionalization assumes QQ independent latent scalar GPs gq(x)∼GP(0,kq(x,x′))g_q(x) \sim \mathcal{GP}(0, k_q(x, x')) and obtains every output as a linear combination:

fp(x)=∑q=1QWp,q gq(x),p=1,…,P,f_p(x) = \sum_{q=1}^Q W_{p, q}\, g_q(x), \qquad p = 1, \ldots, P,

with W∈RP×QW \in \mathbb{R}^{P \times Q} a free mixing matrix. The cross-output covariance is, by independence of the gqg_q,

Cov ⁣[fp(x),fp′(x′)]=∑q=1QWp,qWp′,q kq(x,x′)=[∑qBq kq(x,x′)]p,p′,\mathrm{Cov}\!\bigl[f_p(x), f_{p'}(x')\bigr] = \sum_{q=1}^Q W_{p, q} W_{p', q}\, k_q(x, x') = \bigl[\textstyle\sum_q B_q\,k_q(x, x')\bigr]_{p, p'},

where Bq=wqwq⊤∈RP×PB_q = w_q w_q^\top \in \mathbb{R}^{P \times P} is the rank-1 outer product of the qq-th column of WW with itself. Stacking observations as vec(F)=(F:,1,F:,2,…,F:,P)\mathrm{vec}(F) = (F_{:, 1}, F_{:, 2}, \ldots, F_{:, P}) — output by output — the joint Gram matrix is

KLMC=∑q=1QBq⊗Kq,Kq=kq(X,X)∈RN×N.\mathbf{K}_{\mathrm{LMC}} = \sum_{q=1}^Q B_q \otimes K_q,\qquad K_q = k_q(X, X) \in \mathbb{R}^{N \times N}.

The Kronecker structure tells you that LMC adds no new freedom within each latent: BqB_q is rank-1 by construction. Only the number of latents QQ controls expressiveness. When QQ different lengthscales are needed (e.g. one fast latent + one slow latent), LMC captures it; when one shared kernel suffices, ICM is the cheaper restriction.

Cross-output reconstruction

We hide a chunk of y1y_1 and reconstruct it from y2y_2 via the LMC posterior. Splitting the joint vector into observed indices O\mathcal{O} and unobserved U\mathcal{U}, the standard Gaussian conditional gives

vec(F)U∣vec(Y)O  ∼  N ⁣(KUO (KOO+σn2I)−1 vec(Y)O,    KUU−KUO (KOO+σn2I)−1 KOU).\mathrm{vec}(F)_\mathcal{U} \mid \mathrm{vec}(Y)_\mathcal{O} \;\sim\; \mathcal{N}\!\Bigl(\mathbf{K}_{\mathcal{U}\mathcal{O}}\,(\mathbf{K}_{\mathcal{O}\mathcal{O}} + \sigma_n^2 I)^{-1}\,\mathrm{vec}(Y)_\mathcal{O},\;\; \mathbf{K}_{\mathcal{U}\mathcal{U}} - \mathbf{K}_{\mathcal{U}\mathcal{O}}\,(\mathbf{K}_{\mathcal{O}\mathcal{O}} + \sigma_n^2 I)^{-1}\,\mathbf{K}_{\mathcal{O}\mathcal{U}}\Bigr).

An independent per-output GP would have block-diagonal K\mathbf{K}, so KUO\mathbf{K}_{\mathcal{U}\mathcal{O}} would be zero between y1y_1’s gap entries and y2y_2’s observed entries — the conditional mean would collapse to 0 in the gap. LMC’s KUO\mathbf{K}_{\mathcal{U}\mathcal{O}} has off-block entries via w1,qw2,q kq(x,x′)w_{1, q} w_{2, q}\, k_q(x, x'), and that’s exactly the route by which y2y_2 informs y1y_1.

Build an LMC kernel with two latent processes and compute the dense (2N)×(2N)(2N) \times (2N) Gram for the joint GP. We do the mask handling by stripping the missing entries from vec(Y); the LMC posterior at those held-out indices is what we want.

LMC Gram shape: (120, 120)  (P=2 outputs × N=60 inputs)
<Figure size 1800x500 with 2 Axes>
LMC RMSE on held-out y1 gap: 0.0958

The LMC posterior fills the gap in y1y_1 near the truth even though no y1y_1 values were observed in [−1,1][-1, 1] — the cross-output covariance routes information from y2y_2 through the latent processes back into y1y_1.

2. ICM — the single-Kronecker special case

Definition and derivation

The Intrinsic Coregionalization Model is the LMC restriction to a single shared latent kernel: kq=kk_q = k for all qq. The sum-of-Kroneckers collapses by linearity:

KICM=∑q=1QBq⊗K=(∑qBq)⊗K=B⊗K,B:=∑qwqwq⊤=WW⊤.\mathbf{K}_{\mathrm{ICM}} = \sum_{q=1}^Q B_q \otimes K = \Bigl(\sum_q B_q\Bigr) \otimes K = B \otimes K, \qquad B := \sum_q w_q w_q^\top = W W^\top.

Adding per-output diagonal “nugget” terms κp≥0\kappa_p \ge 0 — extra signal variance unique to each output — gives the standard ICM coregionalization matrix

B=WW⊤+diag(κ)∈RP×P.B = W W^\top + \mathrm{diag}(\kappa) \in \mathbb{R}^{P \times P}.

Why the Kronecker tag matters

Cholesky of a PN×PNPN \times PN matrix is O((PN)3)\mathcal{O}((PN)^3); with P=3,N=60P = 3, N = 60 that is already ∼6×109\sim 6 \times 10^9 flops. The Kronecker identity

(B⊗K)−1=B−1⊗K−1,∣B⊗K∣=∣B∣N ∣K∣P,(B \otimes K)^{-1} = B^{-1} \otimes K^{-1}, \qquad |B \otimes K| = |B|^N\, |K|^P,

lets gaussx.kronecker_mll evaluate the same log-marginal at O(N3+P3)\mathcal{O}(N^3 + P^3) — three orders of magnitude cheaper for N=60,P=3N = 60, P = 3. The tag is what tells the solver which path to take.

pyrox.gp.ICMKernel.cross_covariance_operator returns a lineax operator with the Kronecker tag set, so any downstream gaussx log-marginal / solve routine can pick the Kronecker-exact path automatically — even though as_matrix() exists for inspection.

ICM operator: Kronecker
is_kronecker tag set:    True
Operator dense shape:    (180, 180)
Coregionalization B:
[[1.3  1.1  0.65]
 [1.1  1.74 1.18]
 [0.65 1.18 0.9 ]]

is_kronecker(K_op) → True is the load-bearing fact: it tells gaussx that a single Cholesky on the P×PP \times P block factor and a single Cholesky on the N×NN \times N kernel factor are enough — the dense (PN)×(PN)(PN) \times (PN) system is never materialised internally even though as_matrix() exists for inspection.

3. OILMM — project, run scalar GPs, back-project

The orthogonality assumption and what it buys

OILMM (Bruinsma et al. 2020) is the LMC restricted to a semi-orthogonal mixing matrix:

W∈RP×Q,W⊤W=IQ,Q≤P.W \in \mathbb{R}^{P \times Q}, \qquad W^\top W = I_Q, \qquad Q \le P.

Geometrically, the columns of WW are an orthonormal basis of a QQ-dimensional subspace of RP\mathbb{R}^P, and the projector onto that subspace is WW⊤WW^\top. Within the subspace, WW acts isometrically.

Under this constraint, with per-output Gaussian noise Yn,:=(Wgn,:)+εn,:Y_{n, :} = (W g_{n, :}) + \varepsilon_{n, :}, εn,p∼N(0,σp2)\varepsilon_{n, p} \sim \mathcal{N}(0, \sigma_p^2), the likelihood factorises across latents. Multiplying both sides by W⊤W^\top:

Y~n,q:=(W⊤Yn,:)q=gn,q+ε~n,q,ε~n,q∼N ⁣(0, (W⊤diag(σ2) W)q,q).\widetilde Y_{n, q} := \bigl(W^\top Y_{n, :}\bigr)_q = g_{n, q} + \widetilde\varepsilon_{n, q}, \qquad \widetilde\varepsilon_{n, q} \sim \mathcal{N}\!\Bigl(0,\, (W^\top \mathrm{diag}(\sigma^2)\, W)_{q, q}\Bigr).

Critically, the projected noise is diagonal in qq when WW is orthogonal: there is no off-diagonal coupling between latents after projection (Bruinsma et al. Theorem 1). So the QQ projected problems are fully independent: each is a scalar GP regression on (X,Y~:,q)(X, \widetilde Y_{:, q}) with kernel kqk_q and noise variance σ~q2=∑pWp,q2 σp2\widetilde\sigma_q^2 = \sum_p W_{p, q}^2\, \sigma_p^2.

The recipe

  1. Project observations: Y~=Y W∈RN×Q\widetilde Y = Y\, W \in \mathbb{R}^{N \times Q}.

  2. Fit QQ independent scalar GPs y~:,q∣X∼GP(0,kq)+σ~q2I\widetilde y_{:, q} \mid X \sim \mathcal{GP}(0, k_q) + \widetilde\sigma_q^2 I.

  3. Predict in latent space: g^q(x∗)∼N(μ^q(x∗),σ^q2(x∗))\hat g_q(x_*) \sim \mathcal{N}(\hat\mu_q(x_*), \hat\sigma_q^2(x_*)).

  4. Back-project to output space:

    Y^n,p=∑qWp,q g^n,q,Var ⁣[Y^n,p]=∑qWp,q2 σ^q2.\hat Y_{n, p} = \sum_q W_{p, q}\, \hat g_{n, q}, \qquad \mathrm{Var}\!\bigl[\hat Y_{n, p}\bigr] = \sum_q W_{p, q}^2\, \hat\sigma_q^2.

Cost. Steps 2-3 are QQ scalar GP solves at O(QN3)\mathcal{O}(QN^3) instead of one multi-output solve at O((PN)3)\mathcal{O}((PN)^3). When P≫QP \gg Q — many outputs from a few latents — this is a polynomial-degree win. gaussx.oilmm_project and gaussx.oilmm_back_project are the implementation; OILMMKernel.project / back_project are thin wrappers.

Project the PP-dim observations into the QQ-dim latent space.

Y shape: (80, 4)
Y_latent shape: (80, 2)  (per-latent observations)
per-latent noise variance: [0.04 0.04]

Now run two independent scalar GP regressions — one per latent — using gaussx.log_marginal_likelihood for each, and do simple posterior-mean prediction in latent space before back-projecting.

Back-projected predictive mean shape: (200, 4)  (M=200, P=4)
<Figure size 1800x900 with 4 Axes>

Two scalar GP fits — one per latent — produced predictive means and variances for all four outputs. This is the OILMM win at scale: when PP is large but the cross-output structure is low-rank (Q≪PQ \ll P), you pay QQ scalar GP costs instead of one monolithic PP-output cost.

Sanity check — oilmm_project round-trips

oilmm_back_project is the inverse of oilmm_project for the orthogonal mixing case. Verify directly:

max round-trip error (project → back-project → project): 1.11e-15

Takeaways

  • LMCKernel: K=∑qBq⊗Kq\mathbf{K} = \sum_q B_q \otimes K_q with Bq=wqwq⊤B_q = w_q w_q^\top rank-1 — the most expressive of the three because every latent has its own kernel kqk_q. Cross-output coupling enters via the off-block entries of KUO\mathbf{K}_{\mathcal{U}\mathcal{O}}, which is what lets the conditional posterior fill a gap in y1y_1 from observations of y2y_2.
  • ICMKernel: the LMC restriction kq=kk_q = k, which by linearity collapses to K=B⊗K\mathbf{K} = B \otimes K with B=WW⊤+diag(κ)B = WW^\top + \mathrm{diag}(\kappa). The is_kronecker tag on the returned lineax operator lets gaussx use (B⊗K)−1=B−1⊗K−1(B \otimes K)^{-1} = B^{-1} \otimes K^{-1} for an exact O(N3+P3)\mathcal{O}(N^3 + P^3) solve instead of O((PN)3)\mathcal{O}((PN)^3).
  • OILMMKernel: the LMC restriction W⊤W=IW^\top W = I, which decouples the projected likelihood (ε~q\widetilde\varepsilon_q are independent across qq) so Y~=YW\widetilde Y = YW becomes QQ independent scalar GP problems. Cost drops from O((PN)3)\mathcal{O}((PN)^3) to O(QN3)\mathcal{O}(QN^3) — a polynomial win when P≫QP \gg Q. Round-trip: project(Y, σ²) → Q scalar GPs → back_project(μ, var).