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.

Change of variables from scratch

The one formula the whole curriculum is built on — 1D, 2D, d-D, both directions

00 — Change of variables from scratch

Every Gaussianization method in this curriculum — RBIG, coupling flows, FFJORD, spline flows — is a way of building an invertible map TT that turns messy data into a standard Gaussian. The single fact that makes any of this useful for density estimation is the change-of-variables formula Rezende & Mohamed (2015)Papamakarios et al. (2021): it tells us how a probability density transforms when we push it through TT. Get this one formula and its log⁡det⁡\log\det bookkeeping right and the rest of the course is engineering.

What you will see

1. The 1D formula

Let XX be a continuous random variable with density pXp_X, and let T:R→RT:\mathbb{R}\to\mathbb{R} be a smooth, strictly monotone (hence invertible) map. Define Z=T(X)Z = T(X). Conservation of probability mass over an infinitesimal interval says pX(x) ∣dx∣=pZ(z) ∣dz∣p_X(x)\,|dx| = p_Z(z)\,|dz|, and since dz=T′(x) dxdz = T'(x)\,dx,

pZ(z)=pX(x)∣T′(x)∣∣x=T−1(z),equivalentlypX(x)=pZ(T(x)) ∣T′(x)∣.p_Z(z) = \frac{p_X(x)}{|T'(x)|}\Bigg|_{x = T^{-1}(z)}, \qquad\text{equivalently}\qquad p_X(x) = p_Z\big(T(x)\big)\,\big|T'(x)\big|.

The ∣T′(x)∣|T'(x)| factor is the 1D Jacobian: it is the local stretch/squeeze of the line under TT, and it is exactly what keeps the transformed density normalised.

Worked example. Take X∼LogNormalX \sim \text{LogNormal}, i.e. X=eWX = e^W with W∼N(0,1)W \sim \mathcal{N}(0,1), and the map T(x)=log⁡xT(x) = \log x. Then Z=T(X)=WZ = T(X) = W is standard normal by construction, so we know the answer and can check the formula. Here T′(x)=1/xT'(x) = 1/x, so the formula predicts pZ(z)=pX(ez)⋅ezp_Z(z) = p_X(e^z)\cdot e^z.

<Figure size 1100x400 with 2 Axes>

The red change-of-variables prediction lands exactly on both the empirical histogram of ZZ and the analytic N(0,1)\mathcal{N}(0,1) curve. The ∣T′(x)∣|T'(x)| factor is doing real work: drop it and the prediction would integrate to something other than 1.

2. The dd-dimensional formula

In dd dimensions the scalar derivative T′(x)T'(x) becomes the Jacobian matrix JT(x)J_T(x) with [JT]ij=∂Ti/∂xj[J_T]_{ij} = \partial T_i / \partial x_j, and its absolute determinant replaces ∣T′(x)∣|T'(x)|:

pX(x)=pZ(T(x)) ∣det⁡JT(x)∣.p_X(x) = p_Z\big(T(x)\big)\,\big|\det J_T(x)\big|.

∣det⁡JT(x)∣|\det J_T(x)| is the local volume change: the factor by which TT inflates or shrinks a tiny box of probability mass around xx.

Linear case. For an affine map T(x)=Ax+bT(x) = A x + b the Jacobian is constant, JT=AJ_T = A, so ∣det⁡JT∣=∣det⁡A∣|\det J_T| = |\det A| everywhere. We can read the answer off directly and check it.

autodiff Jacobian J_T(x0):
 [[ 1.3  0.7]
 [-0.4  0.9]]
|det J| (autodiff)      = 1.450000
|det A| (by hand)       = 1.450000
log|det J| (autodiff)   = 0.371564

Seeing ∣det⁡J∣|\det J| as area change. The determinant is not just a number to plug in — it is the factor by which TT scales area locally. We can watch this directly: lay a unit grid over the plane, push every cell through TT, and colour each warped cell by its Jacobian determinant. For the linear map every cell scales by the same ∣det⁡A∣=1.45|\det A| = 1.45 (constant colour). For a nonlinear map the determinant varies with position — which is exactly why in general ∣det⁡JT(x)∣|\det J_T(x)| is a function of xx, evaluated afresh at every point.

<Figure size 1100x460 with 3 Axes>

Left: a unit grid becomes a lattice of identical parallelograms — the linear map stretches area by the constant ∣det⁡A∣=1.45|\det A| = 1.45 everywhere (uniform colour). Right: the same grid warps unevenly; cells in expanding regions are larger and brighter, compressed regions smaller and darker. The change-of-variables formula handles both cases identically because it reads ∣det⁡JT(x)∣|\det J_T(x)| off locally, at each xx.

3. Always work in log-space

Densities span many orders of magnitude and det⁡\det of a d×dd\times d matrix is a product of dd numbers — both overflow/underflow fast. So in practice we never form ∣det⁡J∣|\det J| directly; we accumulate its logarithm:

  log⁡pX(x)=log⁡pZ(T(x))+log⁡∣det⁡JT(x)∣  \boxed{\;\log p_X(x) = \log p_Z\big(T(x)\big) + \log\big|\det J_T(x)\big|\;}

This is the equation a normalizing flow evaluates. jax.numpy.linalg.slogdet returns log⁡∣det⁡∣\log|\det| stably (with a separate sign), which is exactly what we want. Let’s verify the full log-density identity on a nonlinear map in 5D, using an autodiff Jacobian so the check is completely generic.

log p_Z(T(x))     = -7.987257
log|det J_T(x)|   = -0.126274
=> log p_X(x)     = -8.113531

batched log p_X over 4000 points: finite=True

4. The bridge: a gauss_flows bijector reports this exact number

Every bijector in gauss_flows exposes transform_and_log_det(x) -> (z, log|det J|). That second return value is the log⁡∣det⁡JT(x)∣\log|\det J_T(x)| we have been computing — the library does the bookkeeping analytically and cheaply (no d×dd\times d determinant), but it must agree with a brute-force autodiff Jacobian. Let’s confirm that on a MixtureGaussianCDF marginal bijector, the workhorse of Gaussianization.

gauss_flows analytic log|det J| = 1.79662669
autodiff-Jacobian  log|det J|   = 1.79662669
absolute difference             = 2.89e-15

[ok] the library's log-det is the change-of-variables Jacobian.

The two agree to floating-point precision. That is the whole point of this notebook: the abstract formula from §1–§3 is literally what the package returns, so once you trust the formula you can trust the library — and stop computing d×dd\times d determinants by hand, because the analytic log-det is both exact and far cheaper.

Recap

conceptformulain code
1D change of variablespX(x)=pZ(T(x)) ∣T′(x)∣p_X(x)=p_Z(T(x))\,\lvert T'(x)\rvertp_X / abs(jax.grad(T)(x))
dd-D change of variablespX(x)=pZ(T(x)) ∣det⁡JT(x)∣p_X(x)=p_Z(T(x))\,\lvert\det J_T(x)\rvertslogdet(jax.jacfwd(T)(x))
log-density (what flows evaluate)log⁡pX=log⁡pZ(T(x))+log⁡∣det⁡JT∣\log p_X = \log p_Z(T(x)) + \log\lvert\det J_T\rvertlog_pz + log_det
the library’s jobanalytic, cheap log⁡∣det⁡J∣\log\lvert\det J\rvertbij.transform_and_log_det(x)

Next up. A single bijector is rarely enough. In 01 — Composition & the additive log-determinant we stack maps T=TK∘⋯∘T1T = T_K\circ\cdots\circ T_1 and show the log-dets simply add — the rule that lets us build deep flows.

References
  1. Rezende, D. J., & Mohamed, S. (2015). Variational Inference with Normalizing Flows. International Conference on Machine Learning (ICML).
  2. Papamakarios, G., Nalisnick, E., Rezende, D. J., Mohamed, S., & Lakshminarayanan, B. (2021). Normalizing Flows for Probabilistic Modeling and Inference. Journal of Machine Learning Research, 22(57), 1–64.