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.

Fixed orthogonal & PCA warm starts

Freezing a PCA frame as a non-trainable rotation, and warm-starting a trainable Householder stack from a target Q

02 — Fixed orthogonal & PCA warm starts

Notebook 01 made the rotation trainable. But often the best rotation is one you do not train. The eigenvectors of the data covariance — the PCA frame — decorrelate the data in a single shot (notebook 00), so a sensible flow can freeze that frame and spend its parameters on the marginals instead. And when you do want a trainable rotation, you rarely want to start from scratch: initialising it at the PCA frame is a warm start that begins where the data already points.

This notebook covers both, with gauss_flows:

What you will see

1. The PCA frame as a fixed rotation

gf.FixedRotation.from_data(X) computes the eigenvectors of Cov(X)\mathrm{Cov}(X) and stores the orthogonal matrix QQ whose rows are the principal axes, in descending-eigenvalue order. The map y=Qxy = Qx is the decorrelating PCA projection — a pure rotation, so ∣det⁡Q∣=1|\det Q| = 1 and its log-det is exactly 0 (no whitening / rescale; that is left to the marginal step, cf. notebook 00 §2). We fit it to a correlated cloud and check it both rotates and decorrelates.

orthogonal? ||Q^T Q - I|| = 8.9e-16
log_det = +0.0e+00   round-trip = 1.1e-15
max |off-diagonal| covariance:  before = 0.753   after = 1.16e-15
<Figure size 1400x410 with 4 Axes>

The frame rotates the cloud onto its principal axes and the output covariance is diagonal (off-diagonals drop from ∼\simO(1) to ∼10−15\sim10^{-15}). Note it does not rescale — the axis variances are the eigenvalues, still unequal — which is exactly the point: the rotation decorrelates, the marginal step that follows it standardises. log-det stays 0.

2. Why freeze it

A PCA frame is fit once and should stay put. FixedRotation stores its matrix wrapped in paramax.NonTrainable, so flowjax’s fit_to_data skips it during gradient descent. That is not a cosmetic choice. If you instead expose the rotation as a raw, unconstrained matrix and let Adam touch it, it drifts off the orthogonal manifold — and then ∣det⁡Q∣≠1|\det Q|\neq 1, so a flow that assumes rotations contribute log-det 0 will silently mis-account the likelihood. We demonstrate the drift on a decorrelation proxy.

unconstrained matrix after 400 steps:
  ||W^T W - I|| = 0.221   (left the orthogonal manifold)
  log|det W|    = -0.158   (a flow would wrongly book this as 0)
<Figure size 740x420 with 1 Axes>

Starting exactly at the orthogonal PCA frame, the free matrix walks away from O(d)O(d) within a few hundred steps — it “solves” the decorrelation proxy by quietly rescaling, and now its true log-det is ≠0\neq 0. FixedRotation avoids this by freezing the frame; HouseholderRotation/OrthogonalRotation (notebook

  1. avoid it by parameterising orthogonality so every step stays on the manifold. The lesson: never train a rotation as a raw matrix. Either freeze it, or parameterise it.

This is the “fix the frame, learn the marginals” pattern: drop a FixedRotation.from_data between marginal blocks and the rotation does its decorrelating job for free while the trainable marginals do the standardising.

3. Warm-starting a trainable stack from a target QQ

What if we want the rotation trainable and want it to start at the PCA frame? We need to set a HouseholderRotation’s reflection vectors so that its product equals a given QQ. That is a closed-form linear-algebra problem — the Householder decomposition behind QR: zero out QQ column by column with reflections, then absorb the residual ±1\pm 1 diagonal as axis reflections. The reflectors {uk}\{u_k\} satisfy H(u1)⋯H(um)=QH(u_1)\cdots H(u_m) = Q.

decomposed PCA frame into 4 reflections
warm-started HouseholderRotation reproduces Q? ||Q - Q_warm|| = 4.4e-16
matches FixedRotation output on data? max|Δ| = 4.4e-15

The trainable stack now starts at the PCA frame to machine precision — it is numerically identical to the FixedRotation, but its reflection vectors are free to move. We contrast a cold start (random reflectors) against this warm start while training the same decorrelation objective (now safely, because the stack stays orthogonal at every step).

cold start: off-diag loss 6.26e+00 -> 3.83e-19
warm start: off-diag loss 3.17e-30 -> 3.58e-19  (begins at the answer)
<Figure size 760x430 with 1 Axes>

The warm-started stack begins already decorrelated (loss ∼10−30\sim10^{-30}) — it inherited the PCA solution — while the cold start has to descend from ∼6\sim6. Both reach machine zero, confirming the trainable stack can learn the frame; the warm start just gets it for free and then fine-tunes from a sensible place. This is the bridge between notebook 01’s trainable rotation and this notebook’s fixed one: decompose → initialise → fine-tune, the same recipe RBIG uses to warm-start parametric flows (Part 3).

Recap

toolrotation islog-detuse when
gf.FixedRotation.from_datafrozen PCA frame (NonTrainable)0fix the frame, learn only marginals
raw trainable matrixdrifts off O(d)O(d)wrong✗ never — log-det silently corrupts
gf.HouseholderRotation (warm-started)trainable, init at QQ0start at PCA, fine-tune by NLL

The Householder decomposition turns any target QQ — a PCA frame, a previous layer’s rotation — into an exact initialisation for a trainable stack.

Next up. Rotations are the dense orthogonal mixers. Image and high-dimensional flows need cheap structured linear layers with tractable log-dets: 03 — Structured linear layers covers the invertible 1×11\times1 convolution (LU-parameterised, log⁡∣det⁡∣=∑log⁡∣s∣\log|\det| = \sum\log|s|) and ActNorm, the data-dependent affine that makes deep stacks trainable.