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.

Conditional marginals & density estimation

A continuous context y: estimate p(x|y) across the whole range, verify each slice Gaussianizes to N(0,I), and see when y-dependent marginals suffice versus when the couplings must carry it

01 — Conditional marginals & density estimation

In 00 the context was a discrete class label. The more common case is a continuous context yy: a covariate, a sensor setting, a forcing. The conditional flow then estimates a whole curve of densities p(x∣y)p(x\mid y), with the usual conditional change of variables

p(x∣y)=pZ(Tθ(x;y)) ∣det⁡JTθ(⋅;y)(x)∣,p(x\mid y) = p_Z\big(T_\theta(x; y)\big)\,\big|\det J_{T_\theta(\cdot;y)}(x)\big|,

and — keeping a fixed N(0,I)\mathcal{N}(0,I) base — the map Tθ(⋅∣y)T_\theta(\cdot\mid y) is a genuine conditional Gaussianizer: every slice p(x∣y)p(x\mid y) is carried to the same standard normal. That gives us back the diagnostic notebook 00 had to skip: push a slice’s data through Tθ(⋅∣y)T_\theta(\cdot\mid y) and check it lands on N(0,I)\mathcal{N}(0,I).

The other question this notebook answers is 7.2 — when do you condition the marginals versus the couplings? A conditional marginal makes each coordinate’s CDF depend on yy — cheap, and enough when the coordinates are conditionally independent given yy. The moment yy induces dependence between coordinates, only a conditional coupling can follow it. We make that concrete on a family that bends.

What you will see

1. A conditional family that bends

Draw u∼N(0,I2)u\sim\mathcal{N}(0,I_2) and set x1=0.8 u1x_1 = 0.8\,u_1, x2=0.6 u2+1.5 y tanh⁡(1.2 u1)x_2 = 0.6\,u_2 + 1.5\,y\,\tanh(1.2\,u_1). The context yy controls an S-shaped tilt: at y=0y=0 the two coordinates are independent (x2x_2 is just noise); as ∣y∣|y| grows x2x_2 rises with x1x_1 for y>0y>0 and falls for y<0y<0, tilting and bending the cloud. So yy changes both the shape of p(x∣y)p(x\mid y) and the dependence between x1x_1 and x2x_2 — while the tanh⁡\tanh keeps tails bounded (a raw u12u_1^2 bend blows the spline’s support). We standardise xx with fixed stats.

X (5000, 2), context Y (5000, 1), y in [-1.5, 1.5]  (standardised x)
<Figure size 560x500 with 2 Axes>

2. A couplings-conditioned flow

A fixed N(0,I)\mathcal{N}(0,I) base plus a chain of RQSplineCoupling(cond_dim=1) layers (the context yy is concatenated into each conditioner) with Permutes between them. Built as a flowjax Transformed so we can call chain.inverse(x, y) — the Gaussianization map — directly. We train by conditional NLL.

coupling flow: 300 epochs, val NLL +2.2626

3. The learned density, swept across the context

The point of a conditional model: one trained flow gives p(x∣y)p(x\mid y) for any yy. Top row — true data sampled at each yy; bottom row — the flow’s density on a grid at the same yy. The learned banana bends the right way and by the right amount as yy moves from -1.2 to +1.2.

<Figure size 1500x620 with 10 Axes>

4. The Gaussianization check

Because the base is a fixed N(0,I)\mathcal{N}(0,I), the map Tθ(⋅∣y)=T_\theta(\cdot\mid y) = chain.inverse(x, y) should send every slice to the same standard normal. We push each slice’s data through it and look: the conditional bananas all collapse onto the N(0,I)\mathcal{N}(0,I) rings, with moments near (0,1,0,0)(0,1,0,0) regardless of yy.

Gaussianized-slice moments (target 0, 1, 0, 0):
  y=-1: mean=(+0.10,+0.06)  std=(0.99,0.89)  exc-kurt=(-0.20,-0.33)
  y=+0: mean=(-0.09,-0.03)  std=(1.00,0.90)  exc-kurt=(-0.05,-0.07)
  y=+1: mean=(-0.08,+0.01)  std=(1.06,0.92)  exc-kurt=(+0.10,-0.03)
<Figure size 1450x480 with 3 Axes>

Three different bananas, one standard normal — that is the conditional Gaussianizer working. (Compare notebook 00, where SurVAEFlow did not expose this forward map; a flowjax Transformed does.)

5. When do y-dependent marginals suffice? (7.2)

A cheaper option conditions only the margins — here a ConditionalDiagGaussian base (per-coordinate yy-dependent location/scale) with unconditional spline margins and no coupling. Such a model can stretch and shift each axis with yy, but it treats the coordinates as independent given yy, so it cannot represent the banana’s yy-induced dependence. We fit it and compare.


mean conditional NLL (lower is better):
  marginal / diagonal (y -> base only): +2.7301
  coupling (y -> couplings)           : +2.2395
<Figure size 1450x480 with 3 Axes>

The diagonal model widens x2x_2 as ∣y∣|y| grows — it sees the marginal spread — but its density stays an axis-aligned blob, because no elementwise margin can make x2x_2 depend on x1x_1. The coupling flow bends with the data and wins on NLL.

Recap

yy changes...enough to condition...why
per-coordinate location / scalemarginals (or the base)coordinates stay independent given yy
per-coordinate shape (skew, tails)marginals (richer CDF)still elementwise
the dependence between coordinatescouplings (cond_dim)only a coupling reads one coordinate to transform another

So: reach for conditional marginals when yy only reshapes each axis, and for conditional couplings the moment yy couples the axes — which the banana does. With a fixed N(0,I)\mathcal{N}(0,I) base, the trained coupling flow is a conditional Gaussianizer that maps every slice p(x∣y)p(x\mid y) to the same standard normal, as the check in §4 confirmed.

Next up. 02 — Conditional flow as an amortised posterior turns the context into an observation y=Ax+ηy = Ax + \eta and trains p(x∣y)p(x\mid y) as a one-pass posterior for an inverse problem — the bridge to the plug-and-play priors of Part 16.